@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/README.md +135 -28
- package/dist/index.js +634 -143
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
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
|
|
597
|
-
import
|
|
598
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
|
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
|
|
1356
|
-
- "correction" = user correcting a prior wrong assumption
|
|
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
|
|
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.
|
|
1461
|
-
return
|
|
1666
|
+
if (!result || result.startsWith("Error") || result.includes("No memories found")) {
|
|
1667
|
+
return null;
|
|
1462
1668
|
}
|
|
1463
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
1554
|
-
|
|
1555
|
-
const exportPath =
|
|
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
|
-
|
|
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
|
|
1620
|
-
if (
|
|
1621
|
-
augmentedSystemPrompt = activeSystemPrompt +
|
|
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.
|
|
1625
|
-
|
|
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
|
|
1966
|
+
const rawMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1967
|
+
const friendly = humanizeError(rawMessage);
|
|
1698
1968
|
console.error(pc3.red(`
|
|
1699
|
-
|
|
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
|
|
1722
|
-
import
|
|
1723
|
-
import
|
|
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
|
|
2158
|
+
p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 your AI companion"));
|
|
1727
2159
|
let config = loadConfig();
|
|
1728
2160
|
if (!config) {
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
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
|
-
{
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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: "
|
|
2180
|
+
initialValue: "anthropic"
|
|
1800
2181
|
});
|
|
1801
|
-
if (p2.isCancel(
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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(
|
|
1809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1838
|
-
const content =
|
|
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.
|
|
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
|