@aman_asmuei/aman-agent 0.5.0 → 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.
package/dist/index.js CHANGED
@@ -14,7 +14,8 @@ var DEFAULT_HOOKS = {
14
14
  workflowSuggest: true,
15
15
  evalPrompt: true,
16
16
  autoSessionSave: true,
17
- extractMemories: true
17
+ extractMemories: true,
18
+ featureHints: true
18
19
  };
19
20
  var CONFIG_DIR = path.join(os.homedir(), ".aman-agent");
20
21
  var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
@@ -593,10 +594,13 @@ var McpManager = class {
593
594
 
594
595
  // src/agent.ts
595
596
  import * as readline from "readline";
596
- import fs6 from "fs";
597
- import path6 from "path";
598
- import os6 from "os";
597
+ import fs7 from "fs";
598
+ import path7 from "path";
599
+ import os7 from "os";
599
600
  import pc3 from "picocolors";
601
+ import { marked } from "marked";
602
+ import TerminalRenderer from "marked-terminal";
603
+ import logUpdate from "log-update";
600
604
 
601
605
  // src/commands.ts
602
606
  import fs5 from "fs";
@@ -858,7 +862,56 @@ async function handleMemoryCommand(action, args, ctx) {
858
862
  const output = await mcpWrite(ctx, "memory", "memory_forget", { category: args[0] });
859
863
  return { handled: true, output };
860
864
  }
861
- return { handled: true, output: pc.yellow(`Unknown action: /memory ${action}. Use /memory [search|clear].`) };
865
+ if (action === "timeline") {
866
+ if (!ctx.mcpManager) {
867
+ return { handled: true, output: pc.red("Memory not available: MCP not connected.") };
868
+ }
869
+ try {
870
+ const result = await ctx.mcpManager.callTool("memory_recall", { query: "*", limit: 500 });
871
+ if (result.startsWith("Error") || result.includes("No memories found")) {
872
+ return { handled: true, output: pc.dim("No memories yet. Start chatting and I'll remember what matters.") };
873
+ }
874
+ try {
875
+ const memories = JSON.parse(result);
876
+ if (Array.isArray(memories) && memories.length > 0) {
877
+ const byDate = /* @__PURE__ */ new Map();
878
+ for (const mem of memories) {
879
+ const date = mem.created_at ? new Date(mem.created_at).toLocaleDateString("en-US", { month: "short", day: "numeric" }) : "Unknown";
880
+ byDate.set(date, (byDate.get(date) || 0) + 1);
881
+ }
882
+ const maxCount = Math.max(...byDate.values());
883
+ const barWidth = 10;
884
+ const lines = [pc.bold("Memory Timeline:"), ""];
885
+ for (const [date, count] of byDate) {
886
+ const filled = Math.round(count / maxCount * barWidth);
887
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
888
+ lines.push(` ${date.padEnd(8)} ${bar} ${count} memories`);
889
+ }
890
+ const tags = /* @__PURE__ */ new Map();
891
+ for (const mem of memories) {
892
+ if (Array.isArray(mem.tags)) {
893
+ for (const tag of mem.tags) {
894
+ tags.set(tag, (tags.get(tag) || 0) + 1);
895
+ }
896
+ }
897
+ }
898
+ lines.push("");
899
+ lines.push(` Total: ${memories.length} memories`);
900
+ if (tags.size > 0) {
901
+ const topTags = [...tags.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag, count]) => `#${tag} (${count})`).join(", ");
902
+ lines.push(` Top tags: ${topTags}`);
903
+ }
904
+ return { handled: true, output: lines.join("\n") };
905
+ }
906
+ } catch {
907
+ }
908
+ const lineCount = result.split("\n").filter((l) => l.trim()).length;
909
+ return { handled: true, output: `Total memories: ~${lineCount} entries.` };
910
+ } catch {
911
+ return { handled: true, output: pc.red("Failed to retrieve memory timeline.") };
912
+ }
913
+ }
914
+ return { handled: true, output: pc.yellow(`Unknown action: /memory ${action}. Use /memory [search|clear|timeline].`) };
862
915
  }
863
916
  function handleStatusCommand(ctx) {
864
917
  const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
@@ -881,15 +934,45 @@ function handleDoctorCommand(ctx) {
881
934
  const amemConnected = mcpToolCount > 0;
882
935
  const status = getEcosystemStatus(mcpToolCount, amemConnected);
883
936
  const lines = [pc.bold("Aman Health Check"), ""];
937
+ let healthy = 0;
938
+ let fixes = 0;
939
+ let suggestions = 0;
884
940
  for (const layer of status.layers) {
885
- const check = layer.exists ? pc.green("\u2713") : pc.red("\u2717");
886
- const name = layer.name.padEnd(12);
887
- const detail = layer.exists ? pc.green(layer.summary) : pc.red("missing");
888
- lines.push(` ${check} ${name} ${detail}`);
941
+ if (layer.exists) {
942
+ lines.push(` ${pc.green("\u2713")} ${layer.name.padEnd(12)} ${pc.green(layer.summary)}`);
943
+ healthy++;
944
+ } else {
945
+ const isRequired = ["identity", "rules"].includes(layer.name.toLowerCase());
946
+ if (isRequired) {
947
+ lines.push(` ${pc.red("\u2717")} ${layer.name.padEnd(12)} ${pc.red("missing")}`);
948
+ lines.push(` ${pc.dim("\u2192 Fix: aman-agent init")}`);
949
+ fixes++;
950
+ } else {
951
+ lines.push(` ${pc.yellow("\u26A0")} ${layer.name.padEnd(12)} ${pc.yellow("empty")}`);
952
+ const cmd = layer.name.toLowerCase() === "workflows" ? "/workflows add <name>" : layer.name.toLowerCase() === "tools" ? "/tools add <name> <type> <desc>" : layer.name.toLowerCase() === "skills" ? "/skills install <name>" : "";
953
+ if (cmd) lines.push(` ${pc.dim(`\u2192 Add with ${cmd}`)}`);
954
+ suggestions++;
955
+ }
956
+ }
889
957
  }
890
958
  lines.push("");
891
959
  lines.push(` ${status.mcpConnected ? pc.green("\u2713") : pc.red("\u2717")} ${"MCP".padEnd(12)} ${status.mcpConnected ? pc.green(`${status.mcpToolCount} tools`) : pc.red("not connected")}`);
960
+ if (!status.mcpConnected) {
961
+ lines.push(` ${pc.dim("\u2192 Fix: ensure npx is available and network is connected")}`);
962
+ fixes++;
963
+ } else {
964
+ healthy++;
965
+ }
892
966
  lines.push(` ${status.amemConnected ? pc.green("\u2713") : pc.red("\u2717")} ${"Memory".padEnd(12)} ${status.amemConnected ? pc.green("connected") : pc.red("not connected")}`);
967
+ if (!status.amemConnected) {
968
+ lines.push(` ${pc.dim("\u2192 Fix: npx @aman_asmuei/amem")}`);
969
+ fixes++;
970
+ } else {
971
+ healthy++;
972
+ }
973
+ const total = healthy + fixes + suggestions;
974
+ lines.push("");
975
+ lines.push(` Overall: ${healthy}/${total} healthy.${fixes > 0 ? ` ${fixes} fix${fixes > 1 ? "es" : ""} needed.` : ""}${suggestions > 0 ? ` ${suggestions} suggestion${suggestions > 1 ? "s" : ""}.` : ""}`);
893
976
  return { handled: true, output: lines.join("\n") };
894
977
  }
895
978
  function handleHelp() {
@@ -904,7 +987,7 @@ function handleHelp() {
904
987
  ` ${pc.cyan("/tools")} View tools [add|remove ...]`,
905
988
  ` ${pc.cyan("/skills")} View skills [install|uninstall ...]`,
906
989
  ` ${pc.cyan("/eval")} View evaluation [milestone ...]`,
907
- ` ${pc.cyan("/memory")} View recent memories [search|clear ...]`,
990
+ ` ${pc.cyan("/memory")} View recent memories [search|clear|timeline]`,
908
991
  ` ${pc.cyan("/status")} Ecosystem dashboard`,
909
992
  ` ${pc.cyan("/doctor")} Health check all layers`,
910
993
  ` ${pc.cyan("/decisions")} View decision log [<project>]`,
@@ -1074,6 +1157,41 @@ var isHookCall = false;
1074
1157
  async function onSessionStart(ctx) {
1075
1158
  let greeting = "";
1076
1159
  let contextInjection = "";
1160
+ let firstRun = false;
1161
+ let resumeTopic;
1162
+ const visibleReminders = [];
1163
+ try {
1164
+ isHookCall = true;
1165
+ const recallResult = await ctx.mcpManager.callTool("memory_recall", { query: "*", limit: 1 });
1166
+ if (!recallResult || recallResult.startsWith("Error") || recallResult.includes("No memories found")) {
1167
+ firstRun = true;
1168
+ }
1169
+ } catch {
1170
+ firstRun = true;
1171
+ } finally {
1172
+ isHookCall = false;
1173
+ }
1174
+ if (firstRun) {
1175
+ contextInjection = `<first-session>
1176
+ This is your FIRST conversation with this user. Introduce yourself warmly:
1177
+ - Share your name and that you're their personal AI companion
1178
+ - Mention you'll remember what matters across conversations
1179
+ - Ask what they'd like to be called
1180
+ - Keep it to 3-4 sentences, natural tone
1181
+ </first-session>`;
1182
+ const timeContext2 = getTimeContext();
1183
+ contextInjection = `<session-context>
1184
+ ${timeContext2}
1185
+ </session-context>
1186
+ ${contextInjection}`;
1187
+ return {
1188
+ greeting: void 0,
1189
+ contextInjection,
1190
+ firstRun,
1191
+ visibleReminders,
1192
+ resumeTopic: void 0
1193
+ };
1194
+ }
1077
1195
  if (ctx.config.memoryRecall) {
1078
1196
  try {
1079
1197
  isHookCall = true;
@@ -1094,6 +1212,10 @@ async function onSessionStart(ctx) {
1094
1212
  if (result && !result.startsWith("Error")) {
1095
1213
  if (greeting) greeting += "\n";
1096
1214
  greeting += result;
1215
+ const topicMatch = result.match(/(?:resume|last|topic)[:\s]*(.+?)(?:\n|$)/i);
1216
+ if (topicMatch) {
1217
+ resumeTopic = topicMatch[1].trim();
1218
+ }
1097
1219
  }
1098
1220
  } catch (err) {
1099
1221
  log.warn("hooks", "identity_summary failed", err);
@@ -1109,6 +1231,10 @@ async function onSessionStart(ctx) {
1109
1231
  const reminderResult = await ctx.mcpManager.callTool("reminder_check", {});
1110
1232
  if (reminderResult && !reminderResult.startsWith("Error") && !reminderResult.includes("No pending")) {
1111
1233
  greeting += "\n\n<pending-reminders>\n" + reminderResult + "\n</pending-reminders>";
1234
+ const lines = reminderResult.split("\n").filter((l) => l.trim().length > 0);
1235
+ for (const line of lines) {
1236
+ visibleReminders.push(line.trim());
1237
+ }
1112
1238
  }
1113
1239
  } catch (err) {
1114
1240
  log.debug("hooks", "reminder_check failed", err);
@@ -1122,7 +1248,10 @@ ${greeting}
1122
1248
  }
1123
1249
  return {
1124
1250
  greeting: greeting || void 0,
1125
- contextInjection: contextInjection || void 0
1251
+ contextInjection: contextInjection || void 0,
1252
+ firstRun,
1253
+ visibleReminders,
1254
+ resumeTopic
1126
1255
  };
1127
1256
  }
1128
1257
  async function onBeforeToolExec(toolName, toolArgs, ctx) {
@@ -1331,9 +1460,7 @@ ${summaryParts.slice(0, 20).join("\n")}
1331
1460
  }
1332
1461
 
1333
1462
  // src/memory-extractor.ts
1334
- var AUTO_STORE_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology"]);
1335
- var CONFIRM_TYPES = /* @__PURE__ */ new Set(["decision", "correction"]);
1336
- var VALID_TYPES = /* @__PURE__ */ new Set([...AUTO_STORE_TYPES, ...CONFIRM_TYPES]);
1463
+ var VALID_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology", "decision", "correction"]);
1337
1464
  var MIN_RESPONSE_LENGTH = 50;
1338
1465
  var MIN_TURNS_BETWEEN_EMPTY = 3;
1339
1466
  var EXTRACTION_PROMPT = `Analyze this conversation turn. Extract any information worth remembering long-term.
@@ -1352,8 +1479,8 @@ Type guide:
1352
1479
  - "fact" = objective information about systems, people, projects
1353
1480
  - "pattern" = recurring behavior, coding style, approach
1354
1481
  - "topology" = how systems/components connect to each other
1355
- - "decision" = explicit choice between alternatives (requires confirmation)
1356
- - "correction" = user correcting a prior wrong assumption (requires confirmation)
1482
+ - "decision" = explicit choice between alternatives
1483
+ - "correction" = user correcting a prior wrong assumption
1357
1484
 
1358
1485
  Rules:
1359
1486
  - Only extract genuinely useful LONG-TERM information
@@ -1382,7 +1509,7 @@ function parseExtractionResult(raw) {
1382
1509
  return [];
1383
1510
  }
1384
1511
  }
1385
- async function extractMemories(userMessage, assistantResponse, client, mcpManager, state, confirmFn) {
1512
+ async function extractMemories(userMessage, assistantResponse, client, mcpManager, state) {
1386
1513
  if (!shouldExtract(assistantResponse, state.turnsSinceLastExtraction, state.lastExtractionCount)) {
1387
1514
  state.turnsSinceLastExtraction++;
1388
1515
  return 0;
@@ -1422,10 +1549,6 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
1422
1549
  }
1423
1550
  } catch {
1424
1551
  }
1425
- if (CONFIRM_TYPES.has(candidate.type)) {
1426
- const confirmed = await confirmFn(candidate.content);
1427
- if (!confirmed) continue;
1428
- }
1429
1552
  try {
1430
1553
  await mcpManager.callTool("memory_store", {
1431
1554
  content: candidate.content,
@@ -1450,24 +1573,111 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
1450
1573
  }
1451
1574
  }
1452
1575
 
1576
+ // src/errors.ts
1577
+ var ERROR_MAPPINGS = [
1578
+ { pattern: /rate.?limit|429/i, message: "Rate limited. I'll retry automatically." },
1579
+ { pattern: /401|unauthorized/i, message: "API key invalid. Run /reconfig to fix." },
1580
+ { pattern: /403|forbidden/i, message: "API key doesn't have access to this model. Try a different model with --model." },
1581
+ { pattern: /fetch failed|network/i, message: "Network error. Check your internet connection." },
1582
+ { pattern: /ECONNREFUSED/i, message: "Can't reach the API. Are you behind a proxy or firewall?" },
1583
+ { pattern: /context.?length/i, message: "Conversation too long. Use /clear to start fresh or I'll auto-trim." },
1584
+ { pattern: /overloaded/i, message: "API is overloaded. Retrying in a moment..." },
1585
+ { pattern: /ETIMEDOUT/i, message: "Request timed out. Retrying..." }
1586
+ ];
1587
+ function humanizeError(message) {
1588
+ for (const mapping of ERROR_MAPPINGS) {
1589
+ if (mapping.pattern.test(message)) {
1590
+ return mapping.message;
1591
+ }
1592
+ }
1593
+ return message;
1594
+ }
1595
+
1596
+ // src/hints.ts
1597
+ import fs6 from "fs";
1598
+ import path6 from "path";
1599
+ import os6 from "os";
1600
+ var HINTS = [
1601
+ {
1602
+ id: "eval",
1603
+ minTurn: 15,
1604
+ condition: () => true,
1605
+ text: "Tip: See how our relationship has evolved with /eval"
1606
+ },
1607
+ {
1608
+ id: "memory-search",
1609
+ minTurn: 3,
1610
+ condition: (ctx) => ctx.memoryCount >= 10,
1611
+ text: "Tip: Search everything I remember with /memory search <query>"
1612
+ },
1613
+ {
1614
+ id: "workflows",
1615
+ minTurn: 5,
1616
+ condition: (ctx) => !ctx.hasWorkflows,
1617
+ text: "Tip: Teach me multi-step processes with /workflows add"
1618
+ },
1619
+ {
1620
+ id: "rules",
1621
+ minTurn: 8,
1622
+ condition: () => true,
1623
+ text: "Tip: Set guardrails for what I should/shouldn't do with /rules"
1624
+ }
1625
+ ];
1626
+ function getHint(state, ctx) {
1627
+ if (state.hintShownThisSession) return null;
1628
+ for (const hint of HINTS) {
1629
+ if (state.turnCount >= hint.minTurn && !state.shownHints.has(hint.id) && hint.condition(ctx)) {
1630
+ state.shownHints.add(hint.id);
1631
+ state.hintShownThisSession = true;
1632
+ return hint.text;
1633
+ }
1634
+ }
1635
+ return null;
1636
+ }
1637
+ var HINTS_FILE = path6.join(os6.homedir(), ".aman-agent", "hints-seen.json");
1638
+ function loadShownHints() {
1639
+ try {
1640
+ if (fs6.existsSync(HINTS_FILE)) {
1641
+ const data = JSON.parse(fs6.readFileSync(HINTS_FILE, "utf-8"));
1642
+ return new Set(Array.isArray(data) ? data : []);
1643
+ }
1644
+ } catch {
1645
+ }
1646
+ return /* @__PURE__ */ new Set();
1647
+ }
1648
+ function saveShownHints(shown) {
1649
+ try {
1650
+ const dir = path6.dirname(HINTS_FILE);
1651
+ fs6.mkdirSync(dir, { recursive: true });
1652
+ fs6.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
1653
+ } catch {
1654
+ }
1655
+ }
1656
+
1453
1657
  // src/agent.ts
1658
+ marked.use({ renderer: new TerminalRenderer() });
1454
1659
  async function recallForMessage(input, mcpManager) {
1455
1660
  try {
1456
1661
  const result = await mcpManager.callTool("memory_recall", {
1457
1662
  query: input,
1458
- limit: 5
1663
+ limit: 5,
1664
+ compact: true
1459
1665
  });
1460
- if (!result || result.startsWith("Error") || result.trim() === "[]") {
1461
- return "";
1666
+ if (!result || result.startsWith("Error") || result.includes("No memories found")) {
1667
+ return null;
1462
1668
  }
1463
- return `
1669
+ const tokenEstimate = Math.round(result.split(/\s+/).filter(Boolean).length * 1.3);
1670
+ return {
1671
+ text: `
1464
1672
 
1465
1673
  <relevant-memories>
1466
1674
  ${result}
1467
- </relevant-memories>`;
1675
+ </relevant-memories>`,
1676
+ tokenEstimate
1677
+ };
1468
1678
  } catch (err) {
1469
1679
  log.debug("agent", "memory recall failed", err);
1470
- return "";
1680
+ return null;
1471
1681
  }
1472
1682
  }
1473
1683
  function generateSessionId() {
@@ -1479,13 +1689,35 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
1479
1689
  const messages = [];
1480
1690
  const sessionId = generateSessionId();
1481
1691
  const extractorState = { turnsSinceLastExtraction: 0, lastExtractionCount: 0 };
1692
+ const hintState = {
1693
+ turnCount: 0,
1694
+ shownHints: loadShownHints(),
1695
+ hintShownThisSession: false
1696
+ };
1482
1697
  const isRetryable = (err) => err.message.includes("Rate limit") || err.message.includes("rate limit") || err.message.includes("ECONNRESET") || err.message.includes("ETIMEDOUT") || err.message.includes("fetch failed");
1698
+ let responseBuffer = "";
1483
1699
  const onChunkHandler = (chunk) => {
1484
1700
  if (chunk.type === "text" && chunk.text) {
1485
- process.stdout.write(chunk.text);
1701
+ responseBuffer += chunk.text;
1702
+ if (process.stdout.isTTY) {
1703
+ logUpdate(responseBuffer);
1704
+ } else {
1705
+ process.stdout.write(chunk.text);
1706
+ }
1486
1707
  }
1487
1708
  if (chunk.type === "done") {
1488
- process.stdout.write("\n");
1709
+ if (process.stdout.isTTY && responseBuffer.trim()) {
1710
+ try {
1711
+ const rendered = marked(responseBuffer.trim());
1712
+ logUpdate(rendered);
1713
+ logUpdate.done();
1714
+ } catch {
1715
+ logUpdate.done();
1716
+ }
1717
+ } else {
1718
+ process.stdout.write("\n");
1719
+ }
1720
+ responseBuffer = "";
1489
1721
  }
1490
1722
  };
1491
1723
  const rl = readline.createInterface({
@@ -1521,10 +1753,25 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
1521
1753
  const hookCtx = { mcpManager, config: hooksConfig };
1522
1754
  try {
1523
1755
  const session = await onSessionStart(hookCtx);
1524
- if (session.greeting) console.log(pc3.dim(session.greeting));
1756
+ if (!session.firstRun) {
1757
+ if (session.resumeTopic) {
1758
+ console.log(pc3.dim(` Welcome back. Last time we talked about ${session.resumeTopic}`));
1759
+ } else {
1760
+ console.log(pc3.dim(" Welcome back."));
1761
+ }
1762
+ }
1763
+ if (session.visibleReminders && session.visibleReminders.length > 0) {
1764
+ for (const reminder of session.visibleReminders) {
1765
+ console.log(pc3.yellow(` Reminder: ${reminder}`));
1766
+ }
1767
+ }
1525
1768
  if (session.contextInjection) {
1526
1769
  messages.push({ role: "user", content: session.contextInjection });
1527
- messages.push({ role: "assistant", content: "I have context from our previous sessions. How can I help?" });
1770
+ if (session.firstRun) {
1771
+ messages.push({ role: "assistant", content: "acknowledged" });
1772
+ } else {
1773
+ messages.push({ role: "assistant", content: "I have context from our previous sessions. How can I help?" });
1774
+ }
1528
1775
  }
1529
1776
  } catch (err) {
1530
1777
  log.warn("agent", "session start hook failed", err);
@@ -1550,9 +1797,9 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
1550
1797
  }
1551
1798
  if (cmdResult.exportConversation) {
1552
1799
  try {
1553
- const exportDir = path6.join(os6.homedir(), ".aman-agent", "exports");
1554
- fs6.mkdirSync(exportDir, { recursive: true });
1555
- const exportPath = path6.join(exportDir, `${sessionId}.md`);
1800
+ const exportDir = path7.join(os7.homedir(), ".aman-agent", "exports");
1801
+ fs7.mkdirSync(exportDir, { recursive: true });
1802
+ const exportPath = path7.join(exportDir, `${sessionId}.md`);
1556
1803
  const lines = [
1557
1804
  `# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
1558
1805
  `**Model:** ${model}`,
@@ -1566,7 +1813,7 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
1566
1813
  lines.push(`${label} ${msg.content}`, "");
1567
1814
  }
1568
1815
  }
1569
- fs6.writeFileSync(exportPath, lines.join("\n"), "utf-8");
1816
+ fs7.writeFileSync(exportPath, lines.join("\n"), "utf-8");
1570
1817
  console.log(pc3.green(`Exported to ${exportPath}`));
1571
1818
  } catch {
1572
1819
  console.log(pc3.red("Failed to export conversation."));
@@ -1615,14 +1862,19 @@ ${wfMatch.steps}
1615
1862
  await trimConversation(messages, client);
1616
1863
  messages.push({ role: "user", content: input });
1617
1864
  let augmentedSystemPrompt = activeSystemPrompt;
1865
+ let memoryTokens = 0;
1618
1866
  if (mcpManager) {
1619
- const memories = await recallForMessage(input, mcpManager);
1620
- if (memories) {
1621
- augmentedSystemPrompt = activeSystemPrompt + memories;
1867
+ const recall = await recallForMessage(input, mcpManager);
1868
+ if (recall) {
1869
+ augmentedSystemPrompt = activeSystemPrompt + recall.text;
1870
+ memoryTokens = recall.tokenEstimate;
1622
1871
  }
1623
1872
  }
1624
- process.stdout.write(pc3.cyan(`
1625
- ${aiName} > `));
1873
+ const divider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - aiName.length - 2);
1874
+ process.stdout.write(`
1875
+ ${pc3.cyan(pc3.bold(aiName))} ${pc3.dim(divider)}
1876
+
1877
+ `);
1626
1878
  try {
1627
1879
  let response = await withRetry(
1628
1880
  () => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
@@ -1649,6 +1901,15 @@ ${aiName} > `));
1649
1901
  process.stdout.write(pc3.dim(` [using ${toolUse.name}...]
1650
1902
  `));
1651
1903
  const result = await mcpManager.callTool(toolUse.name, toolUse.input);
1904
+ const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
1905
+ if (!skipLogging) {
1906
+ mcpManager.callTool("memory_log", {
1907
+ session_id: sessionId,
1908
+ role: "system",
1909
+ content: `[tool:${toolUse.name}] input=${JSON.stringify(toolUse.input).slice(0, 500)} result=${result.slice(0, 500)}`
1910
+ }).catch(() => {
1911
+ });
1912
+ }
1652
1913
  return {
1653
1914
  type: "tool_result",
1654
1915
  tool_use_id: toolUse.id,
@@ -1666,24 +1927,21 @@ ${aiName} > `));
1666
1927
  );
1667
1928
  messages.push(response.message);
1668
1929
  }
1930
+ const footerParts = [];
1931
+ if (memoryTokens > 0) footerParts.push(`memories: ~${memoryTokens} tokens`);
1932
+ const footer = footerParts.length > 0 ? ` ${footerParts.join(" | ")}` : "";
1933
+ const footerDivider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - footer.length - 1);
1934
+ process.stdout.write(pc3.dim(` ${footerDivider}${footer}
1935
+ `));
1669
1936
  if (mcpManager && hooksConfig?.extractMemories) {
1670
1937
  const assistantText = typeof response.message.content === "string" ? response.message.content : response.message.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
1671
1938
  if (assistantText) {
1672
- const confirmFn = async (content) => {
1673
- return new Promise((resolve) => {
1674
- rl.question(
1675
- pc3.dim(` Remember: "${content}"? (y/N) `),
1676
- (answer) => resolve(answer.toLowerCase() === "y")
1677
- );
1678
- });
1679
- };
1680
1939
  const count = await extractMemories(
1681
1940
  input,
1682
1941
  assistantText,
1683
1942
  client,
1684
1943
  mcpManager,
1685
- extractorState,
1686
- confirmFn
1944
+ extractorState
1687
1945
  );
1688
1946
  if (count > 0) {
1689
1947
  process.stdout.write(pc3.dim(` [${count} memory${count > 1 ? "ies" : ""} stored]
@@ -1693,11 +1951,22 @@ ${aiName} > `));
1693
1951
  } else {
1694
1952
  extractorState.turnsSinceLastExtraction++;
1695
1953
  }
1954
+ if (hooksConfig?.featureHints) {
1955
+ hintState.turnCount++;
1956
+ const hasWorkflows = fs7.existsSync(path7.join(os7.homedir(), ".aflow", "flow.md"));
1957
+ const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
1958
+ const hint = getHint(hintState, { hasWorkflows, memoryCount });
1959
+ if (hint) {
1960
+ process.stdout.write(pc3.dim(` ${hint}
1961
+ `));
1962
+ saveShownHints(hintState.shownHints);
1963
+ }
1964
+ }
1696
1965
  } catch (error) {
1697
- const message = error instanceof Error ? error.message : "Unknown error occurred";
1966
+ const rawMessage = error instanceof Error ? error.message : "Unknown error occurred";
1967
+ const friendly = humanizeError(rawMessage);
1698
1968
  console.error(pc3.red(`
1699
- Error: ${message}`));
1700
- messages.pop();
1969
+ ${friendly}`));
1701
1970
  }
1702
1971
  }
1703
1972
  }
@@ -1718,106 +1987,281 @@ async function saveConversationToMemory(mcpManager, messages, sessionId) {
1718
1987
  }
1719
1988
 
1720
1989
  // src/index.ts
1721
- import fs7 from "fs";
1722
- import path7 from "path";
1723
- import os7 from "os";
1990
+ import fs8 from "fs";
1991
+ import path8 from "path";
1992
+ import os8 from "os";
1993
+
1994
+ // src/presets.ts
1995
+ var PRESETS = {
1996
+ coding: {
1997
+ identity: {
1998
+ personality: "Direct, technical, concise. Shows code over explanation.",
1999
+ style: "Use short answers. Lead with the solution, explain after."
2000
+ },
2001
+ rules: [
2002
+ { category: "response", rule: "Always show code examples, not just descriptions" },
2003
+ { category: "safety", rule: "Never execute destructive commands without confirmation" },
2004
+ { category: "quality", rule: "Follow project conventions over personal preference" }
2005
+ ],
2006
+ workflows: [
2007
+ { name: "debug", description: "Systematic debugging process", steps: ["Reproduce the issue", "Identify root cause", "Propose fix", "Verify fix"] }
2008
+ ]
2009
+ },
2010
+ creative: {
2011
+ identity: {
2012
+ personality: "Warm, imaginative, encouraging. Explores multiple angles.",
2013
+ style: "Use metaphors and vivid language. Ask 'what if' questions."
2014
+ },
2015
+ rules: [
2016
+ { category: "response", rule: "Always offer 2-3 alternative approaches" },
2017
+ { category: "tone", rule: "Encourage experimentation, never dismiss ideas" }
2018
+ ],
2019
+ workflows: [
2020
+ { name: "brainstorm", description: "Creative brainstorming process", steps: ["Explore the problem space", "Generate 5+ ideas", "Evaluate trade-offs", "Refine top 2"] }
2021
+ ]
2022
+ },
2023
+ assistant: {
2024
+ identity: {
2025
+ personality: "Organized, proactive, action-oriented.",
2026
+ style: "Use bullet points and checklists. Summarize key takeaways."
2027
+ },
2028
+ rules: [
2029
+ { category: "response", rule: "End responses with clear next steps when applicable" },
2030
+ { category: "memory", rule: "Always track deadlines and commitments mentioned" }
2031
+ ],
2032
+ workflows: [
2033
+ { name: "plan", description: "Task planning process", steps: ["Clarify the goal", "Break into tasks", "Prioritize", "Set deadlines"] }
2034
+ ]
2035
+ },
2036
+ learning: {
2037
+ identity: {
2038
+ personality: "Patient, curious, Socratic. Builds understanding layer by layer.",
2039
+ style: "Use analogies. Check understanding before moving on."
2040
+ },
2041
+ rules: [
2042
+ { category: "response", rule: "Explain concepts before showing solutions" },
2043
+ { category: "teaching", rule: "Ask a follow-up question to reinforce learning" }
2044
+ ],
2045
+ workflows: []
2046
+ },
2047
+ minimal: {
2048
+ identity: {
2049
+ personality: "Helpful and adaptive. Matches the user's tone and needs.",
2050
+ style: "Clear and concise. Prioritizes usefulness over verbosity."
2051
+ },
2052
+ rules: [],
2053
+ workflows: []
2054
+ }
2055
+ };
2056
+ function applyPreset(name, companionName) {
2057
+ const preset = PRESETS[name];
2058
+ const coreMd = [
2059
+ `# ${companionName}`,
2060
+ "",
2061
+ "## Personality",
2062
+ preset.identity.personality,
2063
+ "",
2064
+ "## Style",
2065
+ preset.identity.style,
2066
+ "",
2067
+ "## Session",
2068
+ "_New companion \u2014 no prior sessions._"
2069
+ ].join("\n");
2070
+ let rulesMd = null;
2071
+ if (preset.rules.length > 0) {
2072
+ const grouped = /* @__PURE__ */ new Map();
2073
+ for (const r of preset.rules) {
2074
+ if (!grouped.has(r.category)) grouped.set(r.category, []);
2075
+ grouped.get(r.category).push(r.rule);
2076
+ }
2077
+ const sections = [...grouped.entries()].map(([cat, rules]) => `## ${cat}
2078
+ ${rules.map((r) => `- ${r}`).join("\n")}`).join("\n\n");
2079
+ rulesMd = `# Guardrails
2080
+
2081
+ ${sections}`;
2082
+ }
2083
+ let flowMd = null;
2084
+ if (preset.workflows.length > 0) {
2085
+ const wfSections = preset.workflows.map((wf) => {
2086
+ const steps = wf.steps.map((s, i) => `${i + 1}. ${s}`).join("\n");
2087
+ return `## ${wf.name}
2088
+ ${wf.description}
2089
+
2090
+ ${steps}`;
2091
+ }).join("\n\n");
2092
+ flowMd = `# Workflows
2093
+
2094
+ ${wfSections}`;
2095
+ }
2096
+ return { coreMd, rulesMd, flowMd };
2097
+ }
2098
+
2099
+ // src/index.ts
2100
+ async function autoDetectConfig() {
2101
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
2102
+ if (anthropicKey) {
2103
+ return { provider: "anthropic", apiKey: anthropicKey, model: "claude-sonnet-4-6" };
2104
+ }
2105
+ const openaiKey = process.env.OPENAI_API_KEY;
2106
+ if (openaiKey) {
2107
+ return { provider: "openai", apiKey: openaiKey, model: "gpt-4o" };
2108
+ }
2109
+ try {
2110
+ const controller = new AbortController();
2111
+ const timeout = setTimeout(() => controller.abort(), 1e3);
2112
+ const res = await fetch("http://localhost:11434/", { signal: controller.signal });
2113
+ clearTimeout(timeout);
2114
+ if (res.ok) {
2115
+ return { provider: "ollama", apiKey: "ollama", model: "llama3.2" };
2116
+ }
2117
+ } catch {
2118
+ }
2119
+ return null;
2120
+ }
2121
+ function bootstrapEcosystem() {
2122
+ const home2 = os8.homedir();
2123
+ const corePath = path8.join(home2, ".acore", "core.md");
2124
+ if (fs8.existsSync(corePath)) return false;
2125
+ fs8.mkdirSync(path8.join(home2, ".acore"), { recursive: true });
2126
+ fs8.writeFileSync(corePath, [
2127
+ "# Aman",
2128
+ "",
2129
+ "## Personality",
2130
+ "Helpful, adaptive, and thoughtful. Matches the user's tone and needs.",
2131
+ "",
2132
+ "## Style",
2133
+ "Clear and concise. Prioritizes usefulness over verbosity.",
2134
+ "",
2135
+ "## Session",
2136
+ "_New companion \u2014 no prior sessions._"
2137
+ ].join("\n"), "utf-8");
2138
+ const rulesDir = path8.join(home2, ".arules");
2139
+ const rulesPath = path8.join(rulesDir, "rules.md");
2140
+ if (!fs8.existsSync(rulesPath)) {
2141
+ fs8.mkdirSync(rulesDir, { recursive: true });
2142
+ fs8.writeFileSync(rulesPath, [
2143
+ "# Guardrails",
2144
+ "",
2145
+ "## safety",
2146
+ "- Never execute destructive commands without explicit confirmation",
2147
+ "- Never expose API keys, passwords, or secrets in responses",
2148
+ "",
2149
+ "## behavior",
2150
+ `- Be honest when uncertain \u2014 say "I'm not sure" rather than guessing`,
2151
+ "- Respect the user's preferences stored in memory"
2152
+ ].join("\n"), "utf-8");
2153
+ }
2154
+ return true;
2155
+ }
1724
2156
  var program = new Command();
1725
2157
  program.name("aman-agent").description("Your AI companion, running locally").version("0.1.0").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).action(async (options) => {
1726
- p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 starting your AI companion"));
2158
+ p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 your AI companion"));
1727
2159
  let config = loadConfig();
1728
2160
  if (!config) {
1729
- p2.log.info("First-time setup \u2014 configure your LLM connection.");
1730
- const provider = await p2.select({
1731
- message: "LLM provider",
1732
- options: [
1733
- {
1734
- value: "anthropic",
1735
- label: "Claude (Anthropic)",
1736
- hint: "recommended"
1737
- },
1738
- { value: "openai", label: "GPT (OpenAI)" },
1739
- { value: "ollama", label: "Ollama (local)", hint: "free, runs offline" }
1740
- ],
1741
- initialValue: "anthropic"
1742
- });
1743
- if (p2.isCancel(provider)) process.exit(0);
1744
- let apiKey = "";
1745
- let defaultModel = "";
1746
- if (provider === "ollama") {
1747
- apiKey = "ollama";
1748
- const modelInput = await p2.text({
1749
- message: "Ollama model name",
1750
- placeholder: "llama3.2",
1751
- defaultValue: "llama3.2"
1752
- });
1753
- if (p2.isCancel(modelInput)) process.exit(0);
1754
- defaultModel = modelInput || "llama3.2";
1755
- } else if (provider === "anthropic") {
1756
- p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
1757
- p2.log.info(pc4.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
1758
- apiKey = await p2.text({
1759
- message: "API key (starts with sk-ant-)",
1760
- validate: (v) => v.length === 0 ? "API key is required" : void 0
1761
- });
1762
- if (p2.isCancel(apiKey)) process.exit(0);
1763
- const modelChoice = await p2.select({
1764
- message: "Claude model",
1765
- options: [
1766
- { value: "claude-sonnet-4-5-20250514", label: "Claude Sonnet 4.5", hint: "fast, recommended" },
1767
- { value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "most capable" },
1768
- { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "fastest, cheapest" },
1769
- { value: "custom", label: "Custom model ID" }
1770
- ],
1771
- initialValue: "claude-sonnet-4-5-20250514"
1772
- });
1773
- if (p2.isCancel(modelChoice)) process.exit(0);
1774
- if (modelChoice === "custom") {
1775
- const customModel = await p2.text({
1776
- message: "Model ID",
1777
- placeholder: "claude-sonnet-4-5-20250514",
1778
- validate: (v) => v.length === 0 ? "Model ID is required" : void 0
1779
- });
1780
- if (p2.isCancel(customModel)) process.exit(0);
1781
- defaultModel = customModel;
1782
- } else {
1783
- defaultModel = modelChoice;
1784
- }
2161
+ const detected = await autoDetectConfig();
2162
+ if (detected) {
2163
+ config = detected;
2164
+ const providerLabel = detected.provider === "anthropic" ? "Anthropic API key" : detected.provider === "openai" ? "OpenAI API key" : "Ollama";
2165
+ const modelLabel = detected.provider === "anthropic" ? "Claude Sonnet 4.6" : detected.provider === "openai" ? "GPT-4o" : "Llama 3.2";
2166
+ p2.log.success(`Auto-detected ${providerLabel}. Using ${modelLabel}.`);
1785
2167
  } else {
1786
- apiKey = await p2.text({
1787
- message: "API key",
1788
- validate: (v) => v.length === 0 ? "API key is required" : void 0
1789
- });
1790
- if (p2.isCancel(apiKey)) process.exit(0);
1791
- const modelChoice = await p2.select({
1792
- message: "OpenAI model",
2168
+ p2.log.info("First-time setup \u2014 configure your LLM connection.");
2169
+ const provider = await p2.select({
2170
+ message: "LLM provider",
1793
2171
  options: [
1794
- { value: "gpt-4o", label: "GPT-4o", hint: "recommended" },
1795
- { value: "gpt-4o-mini", label: "GPT-4o Mini", hint: "faster, cheaper" },
1796
- { value: "o3", label: "o3", hint: "reasoning model" },
1797
- { value: "custom", label: "Custom model ID" }
2172
+ {
2173
+ value: "anthropic",
2174
+ label: "Claude (Anthropic)",
2175
+ hint: "recommended"
2176
+ },
2177
+ { value: "openai", label: "GPT (OpenAI)" },
2178
+ { value: "ollama", label: "Ollama (local)", hint: "free, runs offline" }
1798
2179
  ],
1799
- initialValue: "gpt-4o"
2180
+ initialValue: "anthropic"
1800
2181
  });
1801
- if (p2.isCancel(modelChoice)) process.exit(0);
1802
- if (modelChoice === "custom") {
1803
- const customModel = await p2.text({
1804
- message: "Model ID",
1805
- placeholder: "gpt-4o",
1806
- validate: (v) => v.length === 0 ? "Model ID is required" : void 0
2182
+ if (p2.isCancel(provider)) process.exit(0);
2183
+ let apiKey = "";
2184
+ let defaultModel = "";
2185
+ if (provider === "ollama") {
2186
+ apiKey = "ollama";
2187
+ const modelInput = await p2.text({
2188
+ message: "Ollama model name",
2189
+ placeholder: "llama3.2",
2190
+ defaultValue: "llama3.2"
2191
+ });
2192
+ if (p2.isCancel(modelInput)) process.exit(0);
2193
+ defaultModel = modelInput || "llama3.2";
2194
+ } else if (provider === "anthropic") {
2195
+ p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
2196
+ p2.log.info(pc4.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
2197
+ apiKey = await p2.text({
2198
+ message: "API key (starts with sk-ant-)",
2199
+ validate: (v) => v.length === 0 ? "API key is required" : void 0
1807
2200
  });
1808
- if (p2.isCancel(customModel)) process.exit(0);
1809
- defaultModel = customModel;
2201
+ if (p2.isCancel(apiKey)) process.exit(0);
2202
+ const modelChoice = await p2.select({
2203
+ message: "Claude model",
2204
+ options: [
2205
+ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", hint: "fast, recommended" },
2206
+ { value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "most capable" },
2207
+ { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "fastest, cheapest" },
2208
+ { value: "custom", label: "Custom model ID" }
2209
+ ],
2210
+ initialValue: "claude-sonnet-4-6"
2211
+ });
2212
+ if (p2.isCancel(modelChoice)) process.exit(0);
2213
+ if (modelChoice === "custom") {
2214
+ const customModel = await p2.text({
2215
+ message: "Model ID",
2216
+ placeholder: "claude-sonnet-4-6",
2217
+ validate: (v) => v.length === 0 ? "Model ID is required" : void 0
2218
+ });
2219
+ if (p2.isCancel(customModel)) process.exit(0);
2220
+ defaultModel = customModel;
2221
+ } else {
2222
+ defaultModel = modelChoice;
2223
+ }
1810
2224
  } else {
1811
- defaultModel = modelChoice;
2225
+ apiKey = await p2.text({
2226
+ message: "API key",
2227
+ validate: (v) => v.length === 0 ? "API key is required" : void 0
2228
+ });
2229
+ if (p2.isCancel(apiKey)) process.exit(0);
2230
+ const modelChoice = await p2.select({
2231
+ message: "OpenAI model",
2232
+ options: [
2233
+ { value: "gpt-4o", label: "GPT-4o", hint: "recommended" },
2234
+ { value: "gpt-4o-mini", label: "GPT-4o Mini", hint: "faster, cheaper" },
2235
+ { value: "o3", label: "o3", hint: "reasoning model" },
2236
+ { value: "custom", label: "Custom model ID" }
2237
+ ],
2238
+ initialValue: "gpt-4o"
2239
+ });
2240
+ if (p2.isCancel(modelChoice)) process.exit(0);
2241
+ if (modelChoice === "custom") {
2242
+ const customModel = await p2.text({
2243
+ message: "Model ID",
2244
+ placeholder: "gpt-4o",
2245
+ validate: (v) => v.length === 0 ? "Model ID is required" : void 0
2246
+ });
2247
+ if (p2.isCancel(customModel)) process.exit(0);
2248
+ defaultModel = customModel;
2249
+ } else {
2250
+ defaultModel = modelChoice;
2251
+ }
1812
2252
  }
2253
+ config = { provider, apiKey, model: defaultModel };
2254
+ saveConfig(config);
2255
+ p2.log.success("Config saved to ~/.aman-agent/config.json");
1813
2256
  }
1814
- config = { provider, apiKey, model: defaultModel };
1815
- saveConfig(config);
1816
- p2.log.success("Config saved to ~/.aman-agent/config.json");
1817
2257
  }
1818
2258
  const model = options.model || config.model;
2259
+ const s = p2.spinner();
2260
+ s.start("Loading ecosystem");
2261
+ bootstrapEcosystem();
1819
2262
  const budget = options.budget || void 0;
1820
2263
  const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
2264
+ s.stop("Ecosystem loaded");
1821
2265
  if (layers.length === 0) {
1822
2266
  p2.log.warning(
1823
2267
  "No ecosystem configured. Run " + pc4.bold("npx @aman_asmuei/aman") + " first."
@@ -1831,37 +2275,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1831
2275
  p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc4.dim("(over budget)")}`);
1832
2276
  }
1833
2277
  }
1834
- p2.log.info(`Model: ${pc4.dim(model)}`);
1835
- const corePath = path7.join(os7.homedir(), ".acore", "core.md");
2278
+ const corePath = path8.join(os8.homedir(), ".acore", "core.md");
1836
2279
  let aiName = "Assistant";
1837
- if (fs7.existsSync(corePath)) {
1838
- const content = fs7.readFileSync(corePath, "utf-8");
2280
+ if (fs8.existsSync(corePath)) {
2281
+ const content = fs8.readFileSync(corePath, "utf-8");
1839
2282
  const match = content.match(/^# (.+)$/m);
1840
2283
  if (match) aiName = match[1];
1841
2284
  }
1842
- p2.log.success(`${pc4.bold(aiName)} is ready.`);
1843
2285
  const mcpManager = new McpManager();
1844
- p2.log.step("Connecting to MCP servers...");
2286
+ const mcpSpinner = p2.spinner();
2287
+ mcpSpinner.start("Connecting to MCP servers");
1845
2288
  await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
1846
2289
  await mcpManager.connect("amem", "npx", ["-y", "@aman_asmuei/amem"]);
1847
2290
  const mcpTools = mcpManager.getTools();
2291
+ mcpSpinner.stop("MCP connected");
1848
2292
  if (mcpTools.length > 0) {
1849
2293
  p2.log.success(`${mcpTools.length} MCP tools available`);
1850
2294
  if (mcpTools.some((t) => t.name === "memory_consolidate")) {
2295
+ const memSpinner = p2.spinner();
2296
+ memSpinner.start("Consolidating memory");
1851
2297
  try {
1852
2298
  const consolidateResult = await mcpManager.callTool("memory_consolidate", { dry_run: false });
1853
2299
  if (consolidateResult && !consolidateResult.startsWith("Error")) {
1854
2300
  try {
1855
2301
  const report = JSON.parse(consolidateResult);
2302
+ memSpinner.stop("Memory consolidated");
1856
2303
  if (report.merged > 0 || report.pruned > 0 || report.promoted > 0) {
1857
2304
  p2.log.info(
1858
2305
  `Memory health: ${report.healthScore ?? "?"}% ` + pc4.dim(`(merged ${report.merged}, pruned ${report.pruned}, promoted ${report.promoted})`)
1859
2306
  );
1860
2307
  }
1861
2308
  } catch {
2309
+ memSpinner.stop("Memory consolidated");
1862
2310
  }
2311
+ } else {
2312
+ memSpinner.stop("Memory consolidated");
1863
2313
  }
1864
2314
  } catch {
2315
+ memSpinner.stop("Memory consolidation skipped");
1865
2316
  }
1866
2317
  }
1867
2318
  } else {
@@ -1882,6 +2333,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1882
2333
  } else {
1883
2334
  client = createOpenAIClient(config.apiKey, model);
1884
2335
  }
2336
+ p2.log.success(`${pc4.bold(aiName)} is ready. Model: ${pc4.dim(model)}`);
1885
2337
  await runAgent(
1886
2338
  client,
1887
2339
  systemPrompt,
@@ -1893,5 +2345,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1893
2345
  );
1894
2346
  await mcpManager.disconnect();
1895
2347
  });
2348
+ program.command("init").description("Set up your AI companion with a guided wizard").action(async () => {
2349
+ p2.intro(pc4.bold("aman agent init") + pc4.dim(" \u2014 set up your companion"));
2350
+ const name = await p2.text({
2351
+ message: "What should your companion be called?",
2352
+ placeholder: "Aman",
2353
+ defaultValue: "Aman"
2354
+ });
2355
+ if (p2.isCancel(name)) process.exit(0);
2356
+ const preset = await p2.select({
2357
+ message: "What kind of companion do you need?",
2358
+ options: [
2359
+ { value: "coding", label: "Coding Partner", hint: "direct, technical, concise" },
2360
+ { value: "creative", label: "Creative Collaborator", hint: "warm, imaginative" },
2361
+ { value: "assistant", label: "Personal Assistant", hint: "organized, action-oriented" },
2362
+ { value: "learning", label: "Learning Buddy", hint: "patient, Socratic" },
2363
+ { value: "minimal", label: "Minimal", hint: "just chat, I'll customize later" }
2364
+ ],
2365
+ initialValue: "coding"
2366
+ });
2367
+ if (p2.isCancel(preset)) process.exit(0);
2368
+ const result = applyPreset(preset, name || "Aman");
2369
+ const home2 = os8.homedir();
2370
+ fs8.mkdirSync(path8.join(home2, ".acore"), { recursive: true });
2371
+ fs8.writeFileSync(path8.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
2372
+ p2.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
2373
+ if (result.rulesMd) {
2374
+ fs8.mkdirSync(path8.join(home2, ".arules"), { recursive: true });
2375
+ fs8.writeFileSync(path8.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
2376
+ const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
2377
+ p2.log.success(`${ruleCount} rules set`);
2378
+ }
2379
+ if (result.flowMd) {
2380
+ fs8.mkdirSync(path8.join(home2, ".aflow"), { recursive: true });
2381
+ fs8.writeFileSync(path8.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
2382
+ const wfCount = (result.flowMd.match(/^## /gm) || []).length;
2383
+ p2.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
2384
+ }
2385
+ p2.outro(`Your companion is ready. Run: ${pc4.bold("aman-agent")}`);
2386
+ });
1896
2387
  program.parse();
1897
2388
  //# sourceMappingURL=index.js.map