@aman_asmuei/aman-agent 0.5.1 → 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 +103 -18
- package/dist/index.js +611 -136
- 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,7 +1573,89 @@ 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", {
|
|
@@ -1484,13 +1689,35 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
1484
1689
|
const messages = [];
|
|
1485
1690
|
const sessionId = generateSessionId();
|
|
1486
1691
|
const extractorState = { turnsSinceLastExtraction: 0, lastExtractionCount: 0 };
|
|
1692
|
+
const hintState = {
|
|
1693
|
+
turnCount: 0,
|
|
1694
|
+
shownHints: loadShownHints(),
|
|
1695
|
+
hintShownThisSession: false
|
|
1696
|
+
};
|
|
1487
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 = "";
|
|
1488
1699
|
const onChunkHandler = (chunk) => {
|
|
1489
1700
|
if (chunk.type === "text" && chunk.text) {
|
|
1490
|
-
|
|
1701
|
+
responseBuffer += chunk.text;
|
|
1702
|
+
if (process.stdout.isTTY) {
|
|
1703
|
+
logUpdate(responseBuffer);
|
|
1704
|
+
} else {
|
|
1705
|
+
process.stdout.write(chunk.text);
|
|
1706
|
+
}
|
|
1491
1707
|
}
|
|
1492
1708
|
if (chunk.type === "done") {
|
|
1493
|
-
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 = "";
|
|
1494
1721
|
}
|
|
1495
1722
|
};
|
|
1496
1723
|
const rl = readline.createInterface({
|
|
@@ -1526,10 +1753,25 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1526
1753
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1527
1754
|
try {
|
|
1528
1755
|
const session = await onSessionStart(hookCtx);
|
|
1529
|
-
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
|
+
}
|
|
1530
1768
|
if (session.contextInjection) {
|
|
1531
1769
|
messages.push({ role: "user", content: session.contextInjection });
|
|
1532
|
-
|
|
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
|
+
}
|
|
1533
1775
|
}
|
|
1534
1776
|
} catch (err) {
|
|
1535
1777
|
log.warn("agent", "session start hook failed", err);
|
|
@@ -1555,9 +1797,9 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1555
1797
|
}
|
|
1556
1798
|
if (cmdResult.exportConversation) {
|
|
1557
1799
|
try {
|
|
1558
|
-
const exportDir =
|
|
1559
|
-
|
|
1560
|
-
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`);
|
|
1561
1803
|
const lines = [
|
|
1562
1804
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
1563
1805
|
`**Model:** ${model}`,
|
|
@@ -1571,7 +1813,7 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1571
1813
|
lines.push(`${label} ${msg.content}`, "");
|
|
1572
1814
|
}
|
|
1573
1815
|
}
|
|
1574
|
-
|
|
1816
|
+
fs7.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
1575
1817
|
console.log(pc3.green(`Exported to ${exportPath}`));
|
|
1576
1818
|
} catch {
|
|
1577
1819
|
console.log(pc3.red("Failed to export conversation."));
|
|
@@ -1620,16 +1862,19 @@ ${wfMatch.steps}
|
|
|
1620
1862
|
await trimConversation(messages, client);
|
|
1621
1863
|
messages.push({ role: "user", content: input });
|
|
1622
1864
|
let augmentedSystemPrompt = activeSystemPrompt;
|
|
1865
|
+
let memoryTokens = 0;
|
|
1623
1866
|
if (mcpManager) {
|
|
1624
1867
|
const recall = await recallForMessage(input, mcpManager);
|
|
1625
1868
|
if (recall) {
|
|
1626
1869
|
augmentedSystemPrompt = activeSystemPrompt + recall.text;
|
|
1627
|
-
|
|
1628
|
-
`));
|
|
1870
|
+
memoryTokens = recall.tokenEstimate;
|
|
1629
1871
|
}
|
|
1630
1872
|
}
|
|
1631
|
-
process.stdout.
|
|
1632
|
-
|
|
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
|
+
`);
|
|
1633
1878
|
try {
|
|
1634
1879
|
let response = await withRetry(
|
|
1635
1880
|
() => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
|
|
@@ -1682,24 +1927,21 @@ ${aiName} > `));
|
|
|
1682
1927
|
);
|
|
1683
1928
|
messages.push(response.message);
|
|
1684
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
|
+
`));
|
|
1685
1936
|
if (mcpManager && hooksConfig?.extractMemories) {
|
|
1686
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("");
|
|
1687
1938
|
if (assistantText) {
|
|
1688
|
-
const confirmFn = async (content) => {
|
|
1689
|
-
return new Promise((resolve) => {
|
|
1690
|
-
rl.question(
|
|
1691
|
-
pc3.dim(` Remember: "${content}"? (y/N) `),
|
|
1692
|
-
(answer) => resolve(answer.toLowerCase() === "y")
|
|
1693
|
-
);
|
|
1694
|
-
});
|
|
1695
|
-
};
|
|
1696
1939
|
const count = await extractMemories(
|
|
1697
1940
|
input,
|
|
1698
1941
|
assistantText,
|
|
1699
1942
|
client,
|
|
1700
1943
|
mcpManager,
|
|
1701
|
-
extractorState
|
|
1702
|
-
confirmFn
|
|
1944
|
+
extractorState
|
|
1703
1945
|
);
|
|
1704
1946
|
if (count > 0) {
|
|
1705
1947
|
process.stdout.write(pc3.dim(` [${count} memory${count > 1 ? "ies" : ""} stored]
|
|
@@ -1709,11 +1951,22 @@ ${aiName} > `));
|
|
|
1709
1951
|
} else {
|
|
1710
1952
|
extractorState.turnsSinceLastExtraction++;
|
|
1711
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
|
+
}
|
|
1712
1965
|
} catch (error) {
|
|
1713
|
-
const
|
|
1966
|
+
const rawMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1967
|
+
const friendly = humanizeError(rawMessage);
|
|
1714
1968
|
console.error(pc3.red(`
|
|
1715
|
-
|
|
1716
|
-
messages.pop();
|
|
1969
|
+
${friendly}`));
|
|
1717
1970
|
}
|
|
1718
1971
|
}
|
|
1719
1972
|
}
|
|
@@ -1734,106 +1987,281 @@ async function saveConversationToMemory(mcpManager, messages, sessionId) {
|
|
|
1734
1987
|
}
|
|
1735
1988
|
|
|
1736
1989
|
// src/index.ts
|
|
1737
|
-
import
|
|
1738
|
-
import
|
|
1739
|
-
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
|
+
}
|
|
1740
2156
|
var program = new Command();
|
|
1741
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) => {
|
|
1742
|
-
p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014
|
|
2158
|
+
p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 your AI companion"));
|
|
1743
2159
|
let config = loadConfig();
|
|
1744
2160
|
if (!config) {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
label: "Claude (Anthropic)",
|
|
1752
|
-
hint: "recommended"
|
|
1753
|
-
},
|
|
1754
|
-
{ value: "openai", label: "GPT (OpenAI)" },
|
|
1755
|
-
{ value: "ollama", label: "Ollama (local)", hint: "free, runs offline" }
|
|
1756
|
-
],
|
|
1757
|
-
initialValue: "anthropic"
|
|
1758
|
-
});
|
|
1759
|
-
if (p2.isCancel(provider)) process.exit(0);
|
|
1760
|
-
let apiKey = "";
|
|
1761
|
-
let defaultModel = "";
|
|
1762
|
-
if (provider === "ollama") {
|
|
1763
|
-
apiKey = "ollama";
|
|
1764
|
-
const modelInput = await p2.text({
|
|
1765
|
-
message: "Ollama model name",
|
|
1766
|
-
placeholder: "llama3.2",
|
|
1767
|
-
defaultValue: "llama3.2"
|
|
1768
|
-
});
|
|
1769
|
-
if (p2.isCancel(modelInput)) process.exit(0);
|
|
1770
|
-
defaultModel = modelInput || "llama3.2";
|
|
1771
|
-
} else if (provider === "anthropic") {
|
|
1772
|
-
p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
|
|
1773
|
-
p2.log.info(pc4.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
|
|
1774
|
-
apiKey = await p2.text({
|
|
1775
|
-
message: "API key (starts with sk-ant-)",
|
|
1776
|
-
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
1777
|
-
});
|
|
1778
|
-
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1779
|
-
const modelChoice = await p2.select({
|
|
1780
|
-
message: "Claude model",
|
|
1781
|
-
options: [
|
|
1782
|
-
{ value: "claude-sonnet-4-5-20250514", label: "Claude Sonnet 4.5", hint: "fast, recommended" },
|
|
1783
|
-
{ value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "most capable" },
|
|
1784
|
-
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "fastest, cheapest" },
|
|
1785
|
-
{ value: "custom", label: "Custom model ID" }
|
|
1786
|
-
],
|
|
1787
|
-
initialValue: "claude-sonnet-4-5-20250514"
|
|
1788
|
-
});
|
|
1789
|
-
if (p2.isCancel(modelChoice)) process.exit(0);
|
|
1790
|
-
if (modelChoice === "custom") {
|
|
1791
|
-
const customModel = await p2.text({
|
|
1792
|
-
message: "Model ID",
|
|
1793
|
-
placeholder: "claude-sonnet-4-5-20250514",
|
|
1794
|
-
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
1795
|
-
});
|
|
1796
|
-
if (p2.isCancel(customModel)) process.exit(0);
|
|
1797
|
-
defaultModel = customModel;
|
|
1798
|
-
} else {
|
|
1799
|
-
defaultModel = modelChoice;
|
|
1800
|
-
}
|
|
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}.`);
|
|
1801
2167
|
} else {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
});
|
|
1806
|
-
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1807
|
-
const modelChoice = await p2.select({
|
|
1808
|
-
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",
|
|
1809
2171
|
options: [
|
|
1810
|
-
{
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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" }
|
|
1814
2179
|
],
|
|
1815
|
-
initialValue: "
|
|
2180
|
+
initialValue: "anthropic"
|
|
1816
2181
|
});
|
|
1817
|
-
if (p2.isCancel(
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
|
1823
2200
|
});
|
|
1824
|
-
if (p2.isCancel(
|
|
1825
|
-
|
|
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
|
+
}
|
|
1826
2224
|
} else {
|
|
1827
|
-
|
|
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
|
+
}
|
|
1828
2252
|
}
|
|
2253
|
+
config = { provider, apiKey, model: defaultModel };
|
|
2254
|
+
saveConfig(config);
|
|
2255
|
+
p2.log.success("Config saved to ~/.aman-agent/config.json");
|
|
1829
2256
|
}
|
|
1830
|
-
config = { provider, apiKey, model: defaultModel };
|
|
1831
|
-
saveConfig(config);
|
|
1832
|
-
p2.log.success("Config saved to ~/.aman-agent/config.json");
|
|
1833
2257
|
}
|
|
1834
2258
|
const model = options.model || config.model;
|
|
2259
|
+
const s = p2.spinner();
|
|
2260
|
+
s.start("Loading ecosystem");
|
|
2261
|
+
bootstrapEcosystem();
|
|
1835
2262
|
const budget = options.budget || void 0;
|
|
1836
2263
|
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
2264
|
+
s.stop("Ecosystem loaded");
|
|
1837
2265
|
if (layers.length === 0) {
|
|
1838
2266
|
p2.log.warning(
|
|
1839
2267
|
"No ecosystem configured. Run " + pc4.bold("npx @aman_asmuei/aman") + " first."
|
|
@@ -1847,37 +2275,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1847
2275
|
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc4.dim("(over budget)")}`);
|
|
1848
2276
|
}
|
|
1849
2277
|
}
|
|
1850
|
-
|
|
1851
|
-
const corePath = path7.join(os7.homedir(), ".acore", "core.md");
|
|
2278
|
+
const corePath = path8.join(os8.homedir(), ".acore", "core.md");
|
|
1852
2279
|
let aiName = "Assistant";
|
|
1853
|
-
if (
|
|
1854
|
-
const content =
|
|
2280
|
+
if (fs8.existsSync(corePath)) {
|
|
2281
|
+
const content = fs8.readFileSync(corePath, "utf-8");
|
|
1855
2282
|
const match = content.match(/^# (.+)$/m);
|
|
1856
2283
|
if (match) aiName = match[1];
|
|
1857
2284
|
}
|
|
1858
|
-
p2.log.success(`${pc4.bold(aiName)} is ready.`);
|
|
1859
2285
|
const mcpManager = new McpManager();
|
|
1860
|
-
p2.
|
|
2286
|
+
const mcpSpinner = p2.spinner();
|
|
2287
|
+
mcpSpinner.start("Connecting to MCP servers");
|
|
1861
2288
|
await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
|
|
1862
2289
|
await mcpManager.connect("amem", "npx", ["-y", "@aman_asmuei/amem"]);
|
|
1863
2290
|
const mcpTools = mcpManager.getTools();
|
|
2291
|
+
mcpSpinner.stop("MCP connected");
|
|
1864
2292
|
if (mcpTools.length > 0) {
|
|
1865
2293
|
p2.log.success(`${mcpTools.length} MCP tools available`);
|
|
1866
2294
|
if (mcpTools.some((t) => t.name === "memory_consolidate")) {
|
|
2295
|
+
const memSpinner = p2.spinner();
|
|
2296
|
+
memSpinner.start("Consolidating memory");
|
|
1867
2297
|
try {
|
|
1868
2298
|
const consolidateResult = await mcpManager.callTool("memory_consolidate", { dry_run: false });
|
|
1869
2299
|
if (consolidateResult && !consolidateResult.startsWith("Error")) {
|
|
1870
2300
|
try {
|
|
1871
2301
|
const report = JSON.parse(consolidateResult);
|
|
2302
|
+
memSpinner.stop("Memory consolidated");
|
|
1872
2303
|
if (report.merged > 0 || report.pruned > 0 || report.promoted > 0) {
|
|
1873
2304
|
p2.log.info(
|
|
1874
2305
|
`Memory health: ${report.healthScore ?? "?"}% ` + pc4.dim(`(merged ${report.merged}, pruned ${report.pruned}, promoted ${report.promoted})`)
|
|
1875
2306
|
);
|
|
1876
2307
|
}
|
|
1877
2308
|
} catch {
|
|
2309
|
+
memSpinner.stop("Memory consolidated");
|
|
1878
2310
|
}
|
|
2311
|
+
} else {
|
|
2312
|
+
memSpinner.stop("Memory consolidated");
|
|
1879
2313
|
}
|
|
1880
2314
|
} catch {
|
|
2315
|
+
memSpinner.stop("Memory consolidation skipped");
|
|
1881
2316
|
}
|
|
1882
2317
|
}
|
|
1883
2318
|
} else {
|
|
@@ -1898,6 +2333,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1898
2333
|
} else {
|
|
1899
2334
|
client = createOpenAIClient(config.apiKey, model);
|
|
1900
2335
|
}
|
|
2336
|
+
p2.log.success(`${pc4.bold(aiName)} is ready. Model: ${pc4.dim(model)}`);
|
|
1901
2337
|
await runAgent(
|
|
1902
2338
|
client,
|
|
1903
2339
|
systemPrompt,
|
|
@@ -1909,5 +2345,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1909
2345
|
);
|
|
1910
2346
|
await mcpManager.disconnect();
|
|
1911
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
|
+
});
|
|
1912
2387
|
program.parse();
|
|
1913
2388
|
//# sourceMappingURL=index.js.map
|