open-agents-ai 0.187.428 → 0.187.429

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/dist/index.js CHANGED
@@ -540936,441 +540936,405 @@ import * as path5 from "node:path";
540936
540936
  import * as fs4 from "node:fs";
540937
540937
  function getMemoryStats(workingDir) {
540938
540938
  const stats = {
540939
- zettelkasten: { totalLinks: 0, avgLinksPerEpisode: 0, topNeighbors: [] },
540940
- knowledgeGraph: { totalNodes: 0, totalEdges: 0, nodeTypes: {}, edgeTypes: {} },
540941
- episodes: { total: 0, avgImportance: 0, oldestTs: null, newestTs: null, modalities: {} }
540939
+ zettelkasten: { totalLinks: 0, avgLinksPerEpisode: 0, topLinked: [] },
540940
+ knowledgeGraph: { totalNodes: 0, totalEdges: 0, activeEdges: 0, nodeTypes: {}, edgeTypes: {} },
540941
+ episodes: { total: 0, byModality: {}, avgImportance: 0, recentCount: 0 }
540942
540942
  };
540943
- const memoryDbPath = path5.join(workingDir, ".oa", "episodes.db");
540944
- if (fs4.existsSync(memoryDbPath)) {
540943
+ const episodesDbPath = path5.join(workingDir, ".oa", "episodes.db");
540944
+ if (fs4.existsSync(episodesDbPath)) {
540945
540945
  try {
540946
- const Database2 = __require("better-sqlite3");
540947
- const db = new Database2(memoryDbPath, { readonly: true });
540948
- const episodeCount = db.prepare("SELECT COUNT(*) as count FROM episodes").get();
540949
- stats.episodes.total = episodeCount?.count || 0;
540950
- if (stats.episodes.total > 0) {
540951
- const avgImp = db.prepare("SELECT AVG(importance) as avg FROM episodes").get();
540952
- stats.episodes.avgImportance = avgImp?.avg || 0;
540953
- const oldest = db.prepare("SELECT MIN(timestamp) as min FROM episodes").get();
540954
- stats.episodes.oldestTs = oldest?.min || null;
540955
- const newest = db.prepare("SELECT MAX(timestamp) as max FROM episodes").get();
540956
- stats.episodes.newestTs = newest?.max || null;
540957
- const modalities = db.prepare("SELECT modality, COUNT(*) as count FROM episodes GROUP BY modality").all();
540958
- for (const m2 of modalities) {
540959
- stats.episodes.modalities[m2.modality || "text"] = m2.count;
540960
- }
540961
- }
540962
- db.close();
540946
+ const episodeStore = new EpisodeStore(episodesDbPath);
540947
+ const allEpisodes = episodeStore.search({ limit: 1e4 });
540948
+ stats.episodes.total = allEpisodes.length;
540949
+ if (allEpisodes.length > 0) {
540950
+ const modalityCounts = {};
540951
+ let totalImportance = 0;
540952
+ const oneDayAgo = Date.now() - 864e5;
540953
+ let recentCount = 0;
540954
+ for (const ep of allEpisodes) {
540955
+ const modality = ep.modality || "unknown";
540956
+ modalityCounts[modality] = (modalityCounts[modality] || 0) + 1;
540957
+ totalImportance += ep.importance || 0;
540958
+ if (ep.timestamp > oneDayAgo) recentCount++;
540959
+ }
540960
+ stats.episodes.byModality = modalityCounts;
540961
+ stats.episodes.avgImportance = totalImportance / allEpisodes.length;
540962
+ stats.episodes.recentCount = recentCount;
540963
+ }
540964
+ episodeStore.close();
540963
540965
  } catch (e2) {
540964
540966
  }
540965
540967
  }
540966
540968
  const kgDbPath = path5.join(workingDir, ".oa", "knowledge.db");
540967
540969
  if (fs4.existsSync(kgDbPath)) {
540968
540970
  try {
540969
- const Database2 = __require("better-sqlite3");
540970
- const db = new Database2(kgDbPath, { readonly: true });
540971
- const nodeCount = db.prepare("SELECT COUNT(*) as count FROM nodes").get();
540972
- stats.knowledgeGraph.totalNodes = nodeCount?.count || 0;
540973
- const nodeTypes = db.prepare("SELECT node_type, COUNT(*) as count FROM nodes GROUP BY node_type").all();
540974
- for (const n2 of nodeTypes) {
540975
- stats.knowledgeGraph.nodeTypes[n2.node_type || "unknown"] = n2.count;
540976
- }
540977
- const edgeCount = db.prepare("SELECT COUNT(*) as count FROM edges").get();
540978
- stats.knowledgeGraph.totalEdges = edgeCount?.count || 0;
540979
- const edgeTypes = db.prepare("SELECT edge_type, COUNT(*) as count FROM edges GROUP BY edge_type").all();
540980
- for (const e2 of edgeTypes) {
540981
- stats.knowledgeGraph.edgeTypes[e2.edge_type || "unknown"] = e2.count;
540971
+ const kg = new TemporalGraph(kgDbPath);
540972
+ stats.knowledgeGraph.totalNodes = kg.nodeCount();
540973
+ stats.knowledgeGraph.totalEdges = kg.edgeCount();
540974
+ stats.knowledgeGraph.activeEdges = kg.activeEdgeCount();
540975
+ const nodeTypes = ["entity", "event", "concept", "error", "file", "person", "tool", "location"];
540976
+ for (const nt of nodeTypes) {
540977
+ const nodes = kg.nodesByType(nt, 1e3);
540978
+ if (nodes.length > 0) {
540979
+ stats.knowledgeGraph.nodeTypes[nt] = nodes.length;
540980
+ }
540981
+ }
540982
+ kg.close();
540983
+ } catch (e2) {
540984
+ }
540985
+ }
540986
+ const zettelDbPath = path5.join(workingDir, ".oa", "episodes.db");
540987
+ if (fs4.existsSync(zettelDbPath)) {
540988
+ try {
540989
+ const episodeStore = new EpisodeStore(zettelDbPath);
540990
+ const episodes = episodeStore.search({ limit: 100 });
540991
+ let totalLinks = 0;
540992
+ const linkCounts = [];
540993
+ const episodesWithEmbeddings = episodes.filter((e2) => e2.embedding != null);
540994
+ for (const ep of episodesWithEmbeddings.slice(0, 50)) {
540995
+ try {
540996
+ const neighbors2 = findNeighbors(ep, episodesWithEmbeddings, 5, 0.3);
540997
+ if (neighbors2.length > 0) {
540998
+ totalLinks += neighbors2.length;
540999
+ linkCounts.push({ id: ep.id, linkCount: neighbors2.length });
541000
+ }
541001
+ } catch {
541002
+ }
540982
541003
  }
540983
- db.close();
541004
+ stats.zettelkasten.totalLinks = totalLinks;
541005
+ stats.zettelkasten.avgLinksPerEpisode = episodesWithEmbeddings.length > 0 ? totalLinks / Math.min(episodesWithEmbeddings.length, 50) : 0;
541006
+ stats.zettelkasten.topLinked = linkCounts.sort((a2, b) => b.linkCount - a2.linkCount).slice(0, 5);
541007
+ episodeStore.close();
540984
541008
  } catch (e2) {
540985
541009
  }
540986
541010
  }
540987
541011
  return stats;
540988
541012
  }
540989
541013
  function formatTimestamp(ts) {
540990
- if (!ts) return "N/A";
540991
- const date = new Date(ts);
540992
- return date.toLocaleDateString() + " " + date.toLocaleTimeString();
541014
+ if (!ts) return "unknown";
541015
+ const d2 = new Date(ts);
541016
+ return d2.toLocaleString();
540993
541017
  }
540994
541018
  async function showMemoryMenu(options2) {
540995
- const { rl, workingDir, availableRows } = options2;
541019
+ const { rl, workingDir, availableRows = 20 } = options2;
541020
+ const stats = getMemoryStats(workingDir);
540996
541021
  const items = [
540997
541022
  {
540998
541023
  key: "zettelkasten",
540999
- label: import_chalk.default.cyan("") + " Zettelkasten",
541000
- detail: "Associative note links and neighbor evolution"
541024
+ label: import_chalk.default.cyan("Zettelkasten") + import_chalk.default.gray(` (${stats.zettelkasten.totalLinks} links)`),
541025
+ detail: "Associative note links between episodes"
541001
541026
  },
541002
541027
  {
541003
- key: "knowledge_graph",
541004
- label: import_chalk.default.magenta("") + " Knowledge Graph",
541005
- detail: "Temporal nodes, edges, and concept relationships"
541028
+ key: "knowledge",
541029
+ label: import_chalk.default.magenta("Knowledge Graph") + import_chalk.default.gray(` (${stats.knowledgeGraph.totalNodes} nodes, ${stats.knowledgeGraph.activeEdges} edges)`),
541030
+ detail: "Temporal graph of entities, events, and concepts"
541006
541031
  },
541007
541032
  {
541008
541033
  key: "episodes",
541009
- label: import_chalk.default.green("") + " Episodes",
541010
- detail: "Stored experiences with importance and decay"
541011
- }
541034
+ label: import_chalk.default.yellow("Episodes") + import_chalk.default.gray(` (${stats.episodes.total} total)`),
541035
+ detail: "Stored experiences and observations"
541036
+ },
541037
+ { key: "separator", label: import_chalk.default.gray("─".repeat(30)) },
541038
+ { key: "back", label: import_chalk.default.red("← Back") }
541012
541039
  ];
541040
+ renderInfo2("Memory Browser — Select a system to explore");
541041
+ console.log();
541013
541042
  const result = await tuiSelect({
541014
541043
  items,
541015
- activeKey: "zettelkasten",
541016
- title: "Memory Systems",
541017
541044
  rl,
541018
- availableRows,
541019
- onEnter: (item, { resolve: resolve41 }) => {
541020
- if (item.key === "zettelkasten") {
541021
- showZettelkastenMenu(options2).then(() => {
541022
- resolve41({ confirmed: true, key: "zettelkasten", index: 0 });
541023
- });
541024
- return true;
541025
- }
541026
- if (item.key === "knowledge_graph") {
541027
- showKnowledgeGraphMenu(options2).then(() => {
541028
- resolve41({ confirmed: true, key: "knowledge_graph", index: 1 });
541029
- });
541030
- return true;
541031
- }
541032
- if (item.key === "episodes") {
541033
- showEpisodesMenu(options2).then(() => {
541034
- resolve41({ confirmed: true, key: "episodes", index: 2 });
541035
- });
541036
- return true;
541037
- }
541038
- return false;
541039
- }
541045
+ skipKeys: ["separator"],
541046
+ availableRows
541040
541047
  });
541041
- if (result.confirmed && ["zettelkasten", "knowledge_graph", "episodes"].includes(result.key || "")) {
541042
- await showMemoryMenu(options2);
541048
+ if (!result.confirmed || result.key === "back") {
541049
+ return;
541050
+ }
541051
+ switch (result.key) {
541052
+ case "zettelkasten":
541053
+ await showZettelkastenMenu(options2);
541054
+ break;
541055
+ case "knowledge":
541056
+ await showKnowledgeGraphMenu(options2);
541057
+ break;
541058
+ case "episodes":
541059
+ await showEpisodesMenu(options2);
541060
+ break;
541043
541061
  }
541044
541062
  }
541045
541063
  async function showZettelkastenMenu(options2) {
541046
- const { rl, workingDir, availableRows } = options2;
541047
- const stats = getMemoryStats(workingDir);
541048
- const items = [
541049
- {
541050
- key: "__back__",
541051
- label: import_chalk.default.gray("←") + " Back",
541052
- detail: "Return to memory menu"
541053
- },
541054
- {
541055
- key: "__stats__",
541056
- label: import_chalk.default.yellow("📊") + " Statistics",
541057
- detail: `Links: ${stats.zettelkasten.totalLinks} | Avg: ${stats.zettelkasten.avgLinksPerEpisode.toFixed(2)} per episode`
541058
- }
541059
- ];
541060
- for (const neighbor of stats.zettelkasten.topNeighbors.slice(0, 10)) {
541061
- items.push({
541062
- key: `neighbor_${neighbor.id}`,
541063
- label: import_chalk.default.cyan("◈") + ` ${neighbor.id.slice(0, 16)}...`,
541064
- detail: `${neighbor.linkCount} links`
541065
- });
541064
+ const { rl, workingDir, availableRows = 20 } = options2;
541065
+ const dbPath = path5.join(workingDir, ".oa", "episodes.db");
541066
+ if (!fs4.existsSync(dbPath)) {
541067
+ renderError2("No episode database found at .oa/episodes.db");
541068
+ return;
541066
541069
  }
541067
- const result = await tuiSelect({
541068
- items,
541069
- activeKey: "__back__",
541070
- title: "Zettelkasten Associative Links",
541071
- rl,
541072
- availableRows,
541073
- onEnter: (item, { resolve: resolve41 }) => {
541074
- if (item.key === "__back__") {
541075
- resolve41({ confirmed: false, key: "", index: -1 });
541076
- return true;
541077
- }
541078
- if (item.key === "__stats__") {
541079
- renderInfo2("Zettelkasten Statistics:");
541080
- console.log(` Total Links: ${stats.zettelkasten.totalLinks}`);
541081
- console.log(` Avg Links/Episode: ${stats.zettelkasten.avgLinksPerEpisode.toFixed(2)}`);
541082
- console.log(` Top Neighbors: ${stats.zettelkasten.topNeighbors.length}`);
541083
- resolve41({ confirmed: true, key: "__stats__", index: 1 });
541084
- return true;
541085
- }
541086
- if (item.key?.startsWith("neighbor_")) {
541087
- const neighborId = item.key.replace("neighbor_", "");
541088
- showNeighborDetail(options2, neighborId).then(() => {
541089
- resolve41({ confirmed: true, key: item.key, index: -1 });
541070
+ renderInfo2("Zettelkasten Linked Episodes");
541071
+ console.log();
541072
+ try {
541073
+ const episodeStore = new EpisodeStore(dbPath);
541074
+ const episodes = episodeStore.search({ limit: 100 });
541075
+ const episodesWithEmbeddings = episodes.filter((e2) => e2.embedding != null);
541076
+ const items = [];
541077
+ for (const ep of episodesWithEmbeddings.slice(0, 50)) {
541078
+ try {
541079
+ const neighbors2 = findNeighbors(ep, episodesWithEmbeddings, 5, 0.3);
541080
+ const linkCount = neighbors2.length;
541081
+ const preview = (ep.gist || ep.content || "").slice(0, 50);
541082
+ items.push({
541083
+ key: ep.id,
541084
+ label: import_chalk.default.cyan(ep.id.slice(0, 8)) + import_chalk.default.gray(` (${linkCount} links)`) + " " + import_chalk.default.white(preview + "..."),
541085
+ detail: `Modality: ${ep.modality}, Importance: ${ep.importance}`
541090
541086
  });
541091
- return true;
541087
+ } catch {
541092
541088
  }
541093
- return false;
541094
541089
  }
541095
- });
541096
- if (result.confirmed && result.key !== "__back__") {
541097
- await showZettelkastenMenu(options2);
541090
+ items.push({ key: "separator", label: import_chalk.default.gray("─".repeat(30)) });
541091
+ items.push({ key: "back", label: import_chalk.default.red("← Back") });
541092
+ const result = await tuiSelect({
541093
+ items,
541094
+ rl,
541095
+ skipKeys: ["separator"],
541096
+ availableRows
541097
+ });
541098
+ episodeStore.close();
541099
+ if (result.confirmed && result.key !== "back") {
541100
+ await showNeighborDetail(options2, result.key, episodesWithEmbeddings);
541101
+ }
541102
+ } catch (e2) {
541103
+ renderError2(`Failed to load Zettelkasten: ${e2}`);
541098
541104
  }
541099
541105
  }
541100
- async function showNeighborDetail(options2, neighborId) {
541101
- const { rl, availableRows } = options2;
541102
- const items = [
541103
- {
541104
- key: "__back__",
541105
- label: import_chalk.default.gray("←") + " Back",
541106
- detail: "Return to Zettelkasten menu"
541107
- },
541108
- {
541109
- key: "__info__",
541110
- label: import_chalk.default.cyan("◈") + ` ${neighborId}`,
541111
- detail: "Episode ID"
541112
- }
541113
- ];
541114
- await tuiSelect({
541115
- items,
541116
- activeKey: "__back__",
541117
- title: `Neighbor: ${neighborId.slice(0, 24)}...`,
541118
- rl,
541119
- availableRows,
541120
- onEnter: (item, { resolve: resolve41 }) => {
541121
- if (item.key === "__back__") {
541122
- resolve41({ confirmed: false, key: "", index: -1 });
541123
- return true;
541124
- }
541125
- return false;
541106
+ async function showNeighborDetail(options2, episodeId, allEpisodes) {
541107
+ const { rl, workingDir, availableRows = 20 } = options2;
541108
+ const dbPath = path5.join(workingDir, ".oa", "episodes.db");
541109
+ renderInfo2(`Neighbors of ${episodeId.slice(0, 8)}`);
541110
+ console.log();
541111
+ try {
541112
+ const episodeStore = new EpisodeStore(dbPath);
541113
+ const episode = episodeStore.get(episodeId);
541114
+ if (!episode || !episode.embedding) {
541115
+ renderWarning2("Episode not found or has no embedding");
541116
+ episodeStore.close();
541117
+ return;
541126
541118
  }
541127
- });
541128
- }
541129
- async function showKnowledgeGraphMenu(options2) {
541130
- const { rl, workingDir, availableRows } = options2;
541131
- const stats = getMemoryStats(workingDir);
541132
- const items = [
541133
- {
541134
- key: "__back__",
541135
- label: import_chalk.default.gray("←") + " Back",
541136
- detail: "Return to memory menu"
541137
- },
541138
- {
541139
- key: "__stats__",
541140
- label: import_chalk.default.yellow("📊") + " Statistics",
541141
- detail: `Nodes: ${stats.knowledgeGraph.totalNodes} | Edges: ${stats.knowledgeGraph.totalEdges}`
541119
+ const neighbors2 = findNeighbors(episode, allEpisodes, 10, 0.3);
541120
+ if (neighbors2.length === 0) {
541121
+ renderWarning2("No linked episodes found");
541122
+ episodeStore.close();
541123
+ return;
541142
541124
  }
541143
- ];
541144
- for (const [nodeType, count] of Object.entries(stats.knowledgeGraph.nodeTypes)) {
541145
- items.push({
541146
- key: `node_type_${nodeType}`,
541147
- label: import_chalk.default.magenta("◉") + ` ${nodeType}`,
541148
- detail: `${count} nodes`
541149
- });
541150
- }
541151
- for (const [edgeType, count] of Object.entries(stats.knowledgeGraph.edgeTypes)) {
541152
- items.push({
541153
- key: `edge_type_${edgeType}`,
541154
- label: import_chalk.default.blue("→") + ` ${edgeType}`,
541155
- detail: `${count} edges`
541125
+ const items = neighbors2.map(({ episode: n2, similarity }) => ({
541126
+ key: n2.id,
541127
+ label: import_chalk.default.cyan(n2.id.slice(0, 8)) + " " + import_chalk.default.gray(`(sim: ${similarity.toFixed(2)})`),
541128
+ detail: n2.gist || n2.content?.slice(0, 60) || "No preview"
541129
+ }));
541130
+ items.push({ key: "separator", label: import_chalk.default.gray("─".repeat(30)) });
541131
+ items.push({ key: "back", label: import_chalk.default.red("← Back") });
541132
+ const result = await tuiSelect({
541133
+ items,
541134
+ rl,
541135
+ skipKeys: ["separator"],
541136
+ availableRows
541156
541137
  });
541157
- }
541158
- const result = await tuiSelect({
541159
- items,
541160
- activeKey: "__back__",
541161
- title: "Knowledge Graph — Temporal Nodes & Edges",
541162
- rl,
541163
- availableRows,
541164
- onEnter: (item, { resolve: resolve41 }) => {
541165
- if (item.key === "__back__") {
541166
- resolve41({ confirmed: false, key: "", index: -1 });
541167
- return true;
541168
- }
541169
- if (item.key === "__stats__") {
541170
- renderInfo2("Knowledge Graph Statistics:");
541171
- console.log(` Total Nodes: ${stats.knowledgeGraph.totalNodes}`);
541172
- console.log(` Total Edges: ${stats.knowledgeGraph.totalEdges}`);
541173
- console.log(` Node Types: ${Object.keys(stats.knowledgeGraph.nodeTypes).join(", ") || "none"}`);
541174
- console.log(` Edge Types: ${Object.keys(stats.knowledgeGraph.edgeTypes).join(", ") || "none"}`);
541175
- resolve41({ confirmed: true, key: "__stats__", index: 1 });
541176
- return true;
541177
- }
541178
- resolve41({ confirmed: true, key: item.key, index: -1 });
541179
- return true;
541138
+ episodeStore.close();
541139
+ if (result.confirmed && result.key !== "back") {
541140
+ await showEpisodeDetail(options2, result.key);
541180
541141
  }
541181
- });
541182
- if (result.confirmed && result.key !== "__back__") {
541183
- await showKnowledgeGraphMenu(options2);
541142
+ } catch (e2) {
541143
+ renderError2(`Failed to load neighbors: ${e2}`);
541184
541144
  }
541185
541145
  }
541186
- async function showEpisodesMenu(options2) {
541187
- const { rl, workingDir, availableRows } = options2;
541188
- const stats = getMemoryStats(workingDir);
541189
- const items = [
541190
- {
541191
- key: "__back__",
541192
- label: import_chalk.default.gray("←") + " Back",
541193
- detail: "Return to memory menu"
541194
- },
541195
- {
541196
- key: "__stats__",
541197
- label: import_chalk.default.yellow("📊") + " Statistics",
541198
- detail: `Total: ${stats.episodes.total} | Avg Importance: ${stats.episodes.avgImportance.toFixed(2)}`
541199
- }
541200
- ];
541201
- for (const [modality, count] of Object.entries(stats.episodes.modalities)) {
541202
- items.push({
541203
- key: `modality_${modality}`,
541204
- label: import_chalk.default.green("●") + ` ${modality}`,
541205
- detail: `${count} episodes`
541206
- });
541146
+ async function showKnowledgeGraphMenu(options2) {
541147
+ const { rl, workingDir, availableRows = 20 } = options2;
541148
+ const dbPath = path5.join(workingDir, ".oa", "knowledge.db");
541149
+ if (!fs4.existsSync(dbPath)) {
541150
+ renderError2("No knowledge graph database found at .oa/knowledge.db");
541151
+ return;
541207
541152
  }
541208
- if (stats.episodes.oldestTs && stats.episodes.newestTs) {
541209
- items.push({
541210
- key: "__time_range__",
541211
- label: import_chalk.default.blue("⏱") + " Time Range",
541212
- detail: `${formatTimestamp(stats.episodes.oldestTs)} ${formatTimestamp(stats.episodes.newestTs)}`
541153
+ renderInfo2("Knowledge Graph — Nodes and Edges");
541154
+ console.log();
541155
+ try {
541156
+ const kg = new TemporalGraph(dbPath);
541157
+ const nodeTypes = ["entity", "event", "concept", "error", "file", "person", "tool", "location"];
541158
+ const items = [];
541159
+ for (const nt of nodeTypes) {
541160
+ const nodes = kg.nodesByType(nt, 100);
541161
+ if (nodes.length > 0) {
541162
+ items.push({
541163
+ key: `type:${nt}`,
541164
+ label: import_chalk.default.magenta(nt) + import_chalk.default.gray(` (${nodes.length} nodes)`),
541165
+ detail: `Browse ${nt} nodes`
541166
+ });
541167
+ }
541168
+ }
541169
+ items.push({ key: "separator", label: import_chalk.default.gray("─".repeat(30)) });
541170
+ items.push({ key: "back", label: import_chalk.default.red("← Back") });
541171
+ const result = await tuiSelect({
541172
+ items,
541173
+ rl,
541174
+ skipKeys: ["separator"],
541175
+ availableRows
541213
541176
  });
541177
+ kg.close();
541178
+ if (result.confirmed && result.key !== "back" && result.key?.startsWith("type:")) {
541179
+ await showNodesByType(options2, result.key.replace("type:", ""));
541180
+ }
541181
+ } catch (e2) {
541182
+ renderError2(`Failed to load Knowledge Graph: ${e2}`);
541214
541183
  }
541215
- const episodes = loadRecentEpisodes(workingDir, 20);
541216
- for (const ep of episodes) {
541217
- const preview = ep.text?.slice(0, 40) || ep.gist?.slice(0, 40) || "no content";
541218
- items.push({
541219
- key: `episode_${ep.id}`,
541220
- label: import_chalk.default.green("●") + ` ${ep.id.slice(0, 12)}...`,
541221
- detail: `imp=${ep.importance?.toFixed(1) || "?"} | ${preview}...`
541184
+ }
541185
+ async function showNodesByType(options2, nodeType) {
541186
+ const { rl, workingDir, availableRows = 20 } = options2;
541187
+ const dbPath = path5.join(workingDir, ".oa", "knowledge.db");
541188
+ renderInfo2(`Nodes of type: ${nodeType}`);
541189
+ console.log();
541190
+ try {
541191
+ const kg = new TemporalGraph(dbPath);
541192
+ const nodes = kg.nodesByType(nodeType, 50);
541193
+ const items = nodes.map((n2) => ({
541194
+ key: n2.id,
541195
+ label: import_chalk.default.magenta(n2.text.slice(0, 40)) + import_chalk.default.gray(` (${n2.mentionCount} mentions)`),
541196
+ detail: `First seen: ${formatTimestamp(n2.firstSeen)}`
541197
+ }));
541198
+ items.push({ key: "separator", label: import_chalk.default.gray("─".repeat(30)) });
541199
+ items.push({ key: "back", label: import_chalk.default.red("← Back") });
541200
+ const result = await tuiSelect({
541201
+ items,
541202
+ rl,
541203
+ skipKeys: ["separator"],
541204
+ availableRows
541222
541205
  });
541223
- }
541224
- const result = await tuiSelect({
541225
- items,
541226
- activeKey: "__back__",
541227
- title: "Episodes — Stored Experiences",
541228
- rl,
541229
- availableRows,
541230
- onEnter: (item, { resolve: resolve41 }) => {
541231
- if (item.key === "__back__") {
541232
- resolve41({ confirmed: false, key: "", index: -1 });
541233
- return true;
541234
- }
541235
- if (item.key === "__stats__") {
541236
- renderInfo2("Episode Statistics:");
541237
- console.log(` Total Episodes: ${stats.episodes.total}`);
541238
- console.log(` Avg Importance: ${stats.episodes.avgImportance.toFixed(2)}`);
541239
- console.log(` Oldest: ${formatTimestamp(stats.episodes.oldestTs)}`);
541240
- console.log(` Newest: ${formatTimestamp(stats.episodes.newestTs)}`);
541241
- console.log(` Modalities: ${Object.entries(stats.episodes.modalities).map(([k, v]) => `${k}(${v})`).join(", ") || "none"}`);
541242
- resolve41({ confirmed: true, key: "__stats__", index: 1 });
541243
- return true;
541244
- }
541245
- if (item.key?.startsWith("episode_")) {
541246
- const episodeId = item.key.replace("episode_", "");
541247
- showEpisodeDetail(options2, episodeId).then(() => {
541248
- resolve41({ confirmed: true, key: item.key, index: -1 });
541249
- });
541250
- return true;
541251
- }
541252
- resolve41({ confirmed: true, key: item.key, index: -1 });
541253
- return true;
541206
+ kg.close();
541207
+ if (result.confirmed && result.key !== "back") {
541208
+ await showNodeDetail(options2, result.key);
541254
541209
  }
541255
- });
541256
- if (result.confirmed && result.key !== "__back__") {
541257
- await showEpisodesMenu(options2);
541210
+ } catch (e2) {
541211
+ renderError2(`Failed to load nodes: ${e2}`);
541258
541212
  }
541259
541213
  }
541260
- function loadRecentEpisodes(workingDir, limit) {
541261
- const memoryDbPath = path5.join(workingDir, ".oa", "memory.db");
541262
- if (!fs4.existsSync(memoryDbPath)) return [];
541214
+ async function showNodeDetail(options2, nodeId) {
541215
+ const { rl, workingDir } = options2;
541216
+ const dbPath = path5.join(workingDir, ".oa", "knowledge.db");
541217
+ renderInfo2(`Node: ${nodeId}`);
541218
+ console.log();
541263
541219
  try {
541264
- const Database2 = __require("better-sqlite3");
541265
- const db = new Database2(memoryDbPath, { readonly: true });
541266
- const episodes = db.prepare(`
541267
- SELECT id, text, gist, importance
541268
- FROM episodes
541269
- ORDER BY timestamp DESC
541270
- LIMIT ?
541271
- `).all(limit);
541272
- db.close();
541273
- return episodes;
541220
+ const kg = new TemporalGraph(dbPath);
541221
+ const node = kg.getNode(nodeId);
541222
+ if (!node) {
541223
+ renderWarning2("Node not found");
541224
+ kg.close();
541225
+ return;
541226
+ }
541227
+ console.log(import_chalk.default.bold("Text:"), node.text);
541228
+ console.log(import_chalk.default.bold("Type:"), node.nodeType);
541229
+ console.log(import_chalk.default.bold("Mentions:"), node.mentionCount);
541230
+ console.log(import_chalk.default.bold("First seen:"), formatTimestamp(node.firstSeen));
541231
+ console.log(import_chalk.default.bold("Last seen:"), formatTimestamp(node.lastSeen));
541232
+ console.log();
541233
+ const edges = kg.currentEdges(nodeId);
541234
+ if (edges.length > 0) {
541235
+ console.log(import_chalk.default.bold("Edges:"));
541236
+ for (const e2 of edges.slice(0, 10)) {
541237
+ const otherId = e2.srcId === nodeId ? e2.dstId : e2.srcId;
541238
+ const otherNode = kg.getNode(otherId);
541239
+ console.log(` ${import_chalk.default.cyan(e2.relation)} → ${otherNode?.text || otherId.slice(0, 8)}`);
541240
+ }
541241
+ }
541242
+ kg.close();
541274
541243
  } catch (e2) {
541275
- return [];
541244
+ renderError2(`Failed to load node: ${e2}`);
541276
541245
  }
541246
+ console.log();
541247
+ renderInfo2("Press Enter to go back...");
541248
+ await new Promise((resolve41) => {
541249
+ rl.question("", () => resolve41());
541250
+ });
541277
541251
  }
541278
- async function showEpisodeDetail(options2, episodeId) {
541279
- const { rl, workingDir, availableRows } = options2;
541280
- const episode = loadEpisodeById(workingDir, episodeId);
541281
- if (!episode) {
541282
- renderWarning2(`Episode ${episodeId} not found.`);
541252
+ async function showEpisodesMenu(options2) {
541253
+ const { rl, workingDir, availableRows = 20 } = options2;
541254
+ const dbPath = path5.join(workingDir, ".oa", "episodes.db");
541255
+ if (!fs4.existsSync(dbPath)) {
541256
+ renderError2("No episode database found at .oa/episodes.db");
541283
541257
  return;
541284
541258
  }
541285
- const items = [
541286
- {
541287
- key: "__back__",
541288
- label: import_chalk.default.gray("←") + " Back",
541289
- detail: "Return to Episodes menu"
541290
- },
541291
- {
541292
- key: "__id__",
541293
- label: import_chalk.default.green("●") + " ID",
541294
- detail: episode.id
541295
- },
541296
- {
541297
- key: "__importance__",
541298
- label: import_chalk.default.yellow("★") + " Importance",
541299
- detail: String(episode.importance || 0)
541300
- },
541301
- {
541302
- key: "__modality__",
541303
- label: import_chalk.default.cyan("◈") + " Modality",
541304
- detail: episode.modality || "text"
541305
- },
541306
- {
541307
- key: "__timestamp__",
541308
- label: import_chalk.default.blue("⏱") + " Timestamp",
541309
- detail: formatTimestamp(episode.timestamp || null)
541310
- }
541311
- ];
541312
- if (episode.gist) {
541313
- items.push({
541314
- key: "__gist__",
541315
- label: import_chalk.default.magenta("📝") + " Gist",
541316
- detail: episode.gist.slice(0, 60) + (episode.gist.length > 60 ? "..." : "")
541259
+ renderInfo2("Episodes Recent Experiences");
541260
+ console.log();
541261
+ try {
541262
+ const episodeStore = new EpisodeStore(dbPath);
541263
+ const episodes = episodeStore.search({ limit: 50 });
541264
+ const items = episodes.map((ep) => {
541265
+ const preview = (ep.gist || ep.content || "").slice(0, 50);
541266
+ const timeAgo = formatTimeAgo(ep.timestamp);
541267
+ return {
541268
+ key: ep.id,
541269
+ label: import_chalk.default.yellow(ep.id.slice(0, 8)) + import_chalk.default.gray(` [${ep.modality}]`) + " " + import_chalk.default.white(preview + "..."),
541270
+ detail: `Importance: ${ep.importance}, ${timeAgo}`
541271
+ };
541317
541272
  });
541318
- }
541319
- if (episode.text) {
541320
- const textPreview = episode.text.slice(0, 100).replace(/\n/g, " ");
541321
- items.push({
541322
- key: "__text__",
541323
- label: import_chalk.default.white("📄") + " Text",
541324
- detail: textPreview + (episode.text.length > 100 ? "..." : "")
541273
+ items.push({ key: "separator", label: import_chalk.default.gray("─".repeat(30)) });
541274
+ items.push({ key: "back", label: import_chalk.default.red("← Back") });
541275
+ const result = await tuiSelect({
541276
+ items,
541277
+ rl,
541278
+ skipKeys: ["separator"],
541279
+ availableRows
541325
541280
  });
541326
- }
541327
- await tuiSelect({
541328
- items,
541329
- activeKey: "__back__",
541330
- title: `Episode: ${episodeId.slice(0, 20)}...`,
541331
- rl,
541332
- availableRows,
541333
- onEnter: (item, { resolve: resolve41 }) => {
541334
- if (item.key === "__back__") {
541335
- resolve41({ confirmed: false, key: "", index: -1 });
541336
- return true;
541337
- }
541338
- if (item.key === "__text__" && episode.text) {
541339
- console.log("\n" + import_chalk.default.dim("─".repeat(40)));
541340
- console.log(import_chalk.default.white("Full Text:"));
541341
- console.log(episode.text);
541342
- console.log(import_chalk.default.dim("─".repeat(40)) + "\n");
541343
- resolve41({ confirmed: true, key: "__text__", index: -1 });
541344
- return true;
541345
- }
541346
- if (item.key === "__gist__" && episode.gist) {
541347
- console.log("\n" + import_chalk.default.dim("─".repeat(40)));
541348
- console.log(import_chalk.default.magenta("Full Gist:"));
541349
- console.log(episode.gist);
541350
- console.log(import_chalk.default.dim("─".repeat(40)) + "\n");
541351
- resolve41({ confirmed: true, key: "__gist__", index: -1 });
541352
- return true;
541353
- }
541354
- return false;
541281
+ episodeStore.close();
541282
+ if (result.confirmed && result.key !== "back") {
541283
+ await showEpisodeDetail(options2, result.key);
541355
541284
  }
541356
- });
541285
+ } catch (e2) {
541286
+ renderError2(`Failed to load episodes: ${e2}`);
541287
+ }
541288
+ }
541289
+ function formatTimeAgo(timestamp) {
541290
+ const seconds = Math.floor((Date.now() - timestamp) / 1e3);
541291
+ if (seconds < 60) return "just now";
541292
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
541293
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
541294
+ return `${Math.floor(seconds / 86400)}d ago`;
541357
541295
  }
541358
- function loadEpisodeById(workingDir, id) {
541359
- const memoryDbPath = path5.join(workingDir, ".oa", "memory.db");
541360
- if (!fs4.existsSync(memoryDbPath)) return null;
541296
+ async function showEpisodeDetail(options2, episodeId) {
541297
+ const { rl, workingDir } = options2;
541298
+ const dbPath = path5.join(workingDir, ".oa", "episodes.db");
541299
+ renderInfo2(`Episode: ${episodeId.slice(0, 8)}`);
541300
+ console.log();
541361
541301
  try {
541362
- const Database2 = __require("better-sqlite3");
541363
- const db = new Database2(memoryDbPath, { readonly: true });
541364
- const episode = db.prepare(`
541365
- SELECT id, text, gist, importance, modality, timestamp
541366
- FROM episodes
541367
- WHERE id = ?
541368
- `).get(id);
541369
- db.close();
541370
- return episode || null;
541302
+ const episodeStore = new EpisodeStore(dbPath);
541303
+ const episode = episodeStore.get(episodeId);
541304
+ if (!episode) {
541305
+ renderWarning2("Episode not found");
541306
+ episodeStore.close();
541307
+ return;
541308
+ }
541309
+ console.log(import_chalk.default.bold("ID:"), episode.id);
541310
+ console.log(import_chalk.default.bold("Timestamp:"), formatTimestamp(episode.timestamp));
541311
+ console.log(import_chalk.default.bold("Modality:"), episode.modality);
541312
+ console.log(import_chalk.default.bold("Importance:"), episode.importance);
541313
+ console.log(import_chalk.default.bold("Decay Class:"), episode.decayClass);
541314
+ console.log(import_chalk.default.bold("Strength:"), episode.strength);
541315
+ if (episode.toolName) {
541316
+ console.log(import_chalk.default.bold("Tool:"), episode.toolName);
541317
+ }
541318
+ if (episode.gist) {
541319
+ console.log();
541320
+ console.log(import_chalk.default.bold("Gist:"));
541321
+ console.log(import_chalk.default.gray(episode.gist));
541322
+ }
541323
+ if (episode.content) {
541324
+ console.log();
541325
+ console.log(import_chalk.default.bold("Content:"));
541326
+ const content = episode.content.length > 500 ? episode.content.slice(0, 500) + "..." : episode.content;
541327
+ console.log(import_chalk.default.white(content));
541328
+ }
541329
+ episodeStore.close();
541371
541330
  } catch (e2) {
541372
- return null;
541331
+ renderError2(`Failed to load episode: ${e2}`);
541373
541332
  }
541333
+ console.log();
541334
+ renderInfo2("Press Enter to go back...");
541335
+ await new Promise((resolve41) => {
541336
+ rl.question("", () => resolve41());
541337
+ });
541374
541338
  }
541375
541339
  var import_chalk;
541376
541340
  var init_memory_menu = __esm({
@@ -541379,6 +541343,7 @@ var init_memory_menu = __esm({
541379
541343
  import_chalk = __toESM(require_source(), 1);
541380
541344
  init_tui_select();
541381
541345
  init_render2();
541346
+ init_dist7();
541382
541347
  }
541383
541348
  });
541384
541349
 
@@ -581352,7 +581317,7 @@ import { readFileSync as readFileSync70, writeFileSync as writeFileSync48, appen
581352
581317
  import { existsSync as existsSync88 } from "node:fs";
581353
581318
  import { execSync as execSync56 } from "node:child_process";
581354
581319
  import { homedir as homedir40 } from "node:os";
581355
- function formatTimeAgo(date) {
581320
+ function formatTimeAgo2(date) {
581356
581321
  const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
581357
581322
  if (seconds < 60) return "just now";
581358
581323
  const minutes = Math.floor(seconds / 60);
@@ -586511,7 +586476,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
586511
586476
  if (savedCtx && savedCtx.entries.length > 0) {
586512
586477
  const lastEntry = savedCtx.entries[savedCtx.entries.length - 1];
586513
586478
  const lastTime = lastEntry.savedAt ? new Date(lastEntry.savedAt) : null;
586514
- const timeAgo = lastTime ? formatTimeAgo(lastTime) : "unknown";
586479
+ const timeAgo = lastTime ? formatTimeAgo2(lastTime) : "unknown";
586515
586480
  const lastTask = lastEntry.task?.slice(0, 80) || "unknown";
586516
586481
  setTimeout(async () => {
586517
586482
  writeContent(() => {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.428",
3
+ "version": "0.187.429",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.428",
9
+ "version": "0.187.429",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
@@ -392,9 +392,9 @@
392
392
  "license": "Apache-2.0"
393
393
  },
394
394
  "node_modules/@chainsafe/as-sha256": {
395
- "version": "1.2.0",
396
- "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.2.0.tgz",
397
- "integrity": "sha512-H2BNHQ5C3RS+H0ZvOdovK6GjFAyq5T6LClad8ivwj9Oaiy28uvdsGVS7gNJKuZmg0FGHAI+n7F0Qju6U0QkKDA==",
395
+ "version": "1.2.4",
396
+ "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.2.4.tgz",
397
+ "integrity": "sha512-3GXDysZOKD6cTYbm48lEdXdUbS7cafjXQZfgHOspTByhoGR/JM3KBXyF3vE6bf63ImjNPyoEZwnQcpYPQ6k3bQ==",
398
398
  "license": "Apache-2.0"
399
399
  },
400
400
  "node_modules/@chainsafe/is-ip": {
@@ -481,13 +481,13 @@
481
481
  }
482
482
  },
483
483
  "node_modules/@helia/bitswap": {
484
- "version": "3.2.2",
485
- "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-3.2.2.tgz",
486
- "integrity": "sha512-f+FiGaSaQt070wT8To1e1cRcFIaRMOctrJAEKksOpLB3Y0qMjdqAKALM4jSNxh8mf7jFa5HyXGKIShQWVCxMcg==",
484
+ "version": "3.2.3",
485
+ "resolved": "https://registry.npmjs.org/@helia/bitswap/-/bitswap-3.2.3.tgz",
486
+ "integrity": "sha512-uquUTgYaU5Z08SVFn+Psg1xhkeAqCdLWav0zAaNJE1bQX7UeLItW82+Nh5I40RaH9gMFFqL3IQR0mIzi+bj5Gg==",
487
487
  "license": "Apache-2.0 OR MIT",
488
488
  "dependencies": {
489
489
  "@helia/interface": "^6.2.1",
490
- "@helia/utils": "^2.5.1",
490
+ "@helia/utils": "^2.5.2",
491
491
  "@libp2p/interface": "^3.2.0",
492
492
  "@libp2p/logger": "^6.2.4",
493
493
  "@libp2p/peer-collections": "^7.0.15",
@@ -523,14 +523,14 @@
523
523
  }
524
524
  },
525
525
  "node_modules/@helia/block-brokers": {
526
- "version": "5.2.3",
527
- "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-5.2.3.tgz",
528
- "integrity": "sha512-a6X2a0wYYRfbKhTE98/C6pvwQOtz0gQypQCMMHw3QoVGr15rJ6PxjwX5dMZxKD52GoWkQ9/eR01eGVfkXGtYqw==",
526
+ "version": "5.2.4",
527
+ "resolved": "https://registry.npmjs.org/@helia/block-brokers/-/block-brokers-5.2.4.tgz",
528
+ "integrity": "sha512-UsxtWVy2W95FVM561BZXEnb41RL6pr+cLF+AIxVIaugEvg4edUKh+3sbZXoINtzbqS664Ss2dhisHR1ZnYI57Q==",
529
529
  "license": "Apache-2.0 OR MIT",
530
530
  "dependencies": {
531
- "@helia/bitswap": "^3.2.2",
531
+ "@helia/bitswap": "^3.2.3",
532
532
  "@helia/interface": "^6.2.1",
533
- "@helia/utils": "^2.5.1",
533
+ "@helia/utils": "^2.5.2",
534
534
  "@libp2p/interface": "^3.2.0",
535
535
  "@libp2p/peer-id": "^6.0.6",
536
536
  "@libp2p/utils": "^7.0.15",
@@ -673,9 +673,9 @@
673
673
  }
674
674
  },
675
675
  "node_modules/@helia/utils": {
676
- "version": "2.5.1",
677
- "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-2.5.1.tgz",
678
- "integrity": "sha512-8HI3Ji6tz/C8XVhy/lHy6x/iSKLk7BtjAHQaSU/jrJOthB1hmmXFbo7jrSG9ql6Na7aygj9is4ajIaE/wxfAWg==",
676
+ "version": "2.5.2",
677
+ "resolved": "https://registry.npmjs.org/@helia/utils/-/utils-2.5.2.tgz",
678
+ "integrity": "sha512-6/+RInRpJsJoo13ECQsSkBtK2xUGl/KWaJpBuK9tqg+ng3s5cec2qO9RVhZt4bdkPPqJc/PX+G9zwVhKqp5UwQ==",
679
679
  "license": "Apache-2.0 OR MIT",
680
680
  "dependencies": {
681
681
  "@helia/interface": "^6.2.1",
@@ -1520,9 +1520,9 @@
1520
1520
  }
1521
1521
  },
1522
1522
  "node_modules/@libp2p/webrtc": {
1523
- "version": "6.0.18",
1524
- "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-6.0.18.tgz",
1525
- "integrity": "sha512-EX4lu+96K03V2WfiyAm+/I8VRXwUfIPS6n+NqyypIfqJmTWwTIZL/SsKO54m+R+ccUbSnaoa0eOVTDhFcMgnkw==",
1523
+ "version": "6.0.19",
1524
+ "resolved": "https://registry.npmjs.org/@libp2p/webrtc/-/webrtc-6.0.19.tgz",
1525
+ "integrity": "sha512-BY0WAMfWcmmhlxB3hWafjjfiRtFO0IrfS9ffT8u4nfDG/FT+vrR/CLJtmEEsypuR6cuuPJCWgrKpKQXgjVm7AQ==",
1526
1526
  "license": "Apache-2.0 OR MIT",
1527
1527
  "dependencies": {
1528
1528
  "@chainsafe/is-ip": "^2.1.0",
@@ -1537,7 +1537,6 @@
1537
1537
  "@multiformats/multiaddr-matcher": "^3.0.1",
1538
1538
  "@peculiar/webcrypto": "^1.5.0",
1539
1539
  "@peculiar/x509": "^2.0.0",
1540
- "detect-browser": "^5.3.0",
1541
1540
  "get-port": "^7.1.0",
1542
1541
  "interface-datastore": "^9.0.1",
1543
1542
  "it-length-prefixed": "^10.0.1",
@@ -3894,12 +3893,6 @@
3894
3893
  "npm": "1.2.8000 || >= 1.4.16"
3895
3894
  }
3896
3895
  },
3897
- "node_modules/detect-browser": {
3898
- "version": "5.3.0",
3899
- "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz",
3900
- "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==",
3901
- "license": "MIT"
3902
- },
3903
3896
  "node_modules/detect-libc": {
3904
3897
  "version": "2.1.2",
3905
3898
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -3951,9 +3944,9 @@
3951
3944
  "license": "MIT"
3952
3945
  },
3953
3946
  "node_modules/electron-to-chromium": {
3954
- "version": "1.5.340",
3955
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz",
3956
- "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==",
3947
+ "version": "1.5.341",
3948
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.341.tgz",
3949
+ "integrity": "sha512-1sZTssferjgDgaqRTc0ieP+ozzpOy7LQTPTtEW3yQFn4+ORdIAZWV5BthXPyHF7YqLvFJCUPhNhdAJQYlYUgiw==",
3957
3950
  "license": "ISC",
3958
3951
  "peer": true
3959
3952
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.428",
3
+ "version": "0.187.429",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",