@aman_asmuei/aman-agent 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import { Command } from "commander";
3
3
  import * as p2 from "@clack/prompts";
4
- import pc5 from "picocolors";
4
+ import pc4 from "picocolors";
5
5
 
6
6
  // src/config.ts
7
7
  import fs from "fs";
@@ -172,76 +172,83 @@ function createAnthropicClient(apiKey, model) {
172
172
  const anthropicMessages = toAnthropicMessages(messages);
173
173
  const hasTools = tools && tools.length > 0;
174
174
  try {
175
+ let fullText = "";
176
+ const toolUseBlocks = [];
177
+ let currentBlockType = null;
178
+ let currentBlockIndex = -1;
179
+ const createParams = {
180
+ model,
181
+ max_tokens: 8192,
182
+ system: systemPrompt,
183
+ messages: anthropicMessages,
184
+ stream: true
185
+ };
175
186
  if (hasTools) {
176
- const response = await client.messages.create({
177
- model,
178
- max_tokens: 8192,
179
- system: systemPrompt,
180
- messages: anthropicMessages,
181
- tools: tools.map((t) => ({
182
- name: t.name,
183
- description: t.description,
184
- input_schema: t.input_schema
185
- }))
186
- });
187
- const toolUses = response.content.filter(
188
- (block) => block.type === "tool_use"
189
- ).map((block) => ({
190
- id: block.id,
191
- name: block.name,
192
- input: block.input
187
+ createParams.tools = tools.map((t) => ({
188
+ name: t.name,
189
+ description: t.description,
190
+ input_schema: t.input_schema
193
191
  }));
194
- const textContent = response.content.filter(
195
- (block) => block.type === "text"
196
- ).map((block) => block.text).join("");
197
- if (textContent && toolUses.length === 0) {
198
- onChunk({ type: "text", text: textContent });
199
- onChunk({ type: "done" });
200
- } else if (textContent) {
201
- onChunk({ type: "text", text: textContent });
202
- }
203
- const contentBlocks = response.content.map(
204
- (block) => {
205
- if (block.type === "text") {
206
- return { type: "text", text: block.text };
207
- }
208
- return {
209
- type: "tool_use",
210
- id: block.id,
211
- name: block.name,
212
- input: block.input
213
- };
192
+ }
193
+ const stream = await client.messages.create(
194
+ createParams
195
+ );
196
+ for await (const event of stream) {
197
+ if (event.type === "content_block_start") {
198
+ currentBlockIndex = event.index;
199
+ if (event.content_block.type === "text") {
200
+ currentBlockType = "text";
201
+ } else if (event.content_block.type === "tool_use") {
202
+ currentBlockType = "tool_use";
203
+ toolUseBlocks.push({
204
+ id: event.content_block.id,
205
+ name: event.content_block.name,
206
+ inputJson: ""
207
+ });
214
208
  }
215
- );
216
- return {
217
- message: {
218
- role: "assistant",
219
- content: toolUses.length > 0 ? contentBlocks : textContent
220
- },
221
- toolUses
222
- };
223
- } else {
224
- let fullText = "";
225
- const stream = await client.messages.create({
226
- model,
227
- max_tokens: 8192,
228
- system: systemPrompt,
229
- messages: anthropicMessages,
230
- stream: true
231
- });
232
- for await (const event of stream) {
233
- if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
209
+ } else if (event.type === "content_block_delta") {
210
+ if (currentBlockType === "text" && event.delta.type === "text_delta") {
234
211
  const text2 = event.delta.text;
235
212
  fullText += text2;
236
213
  onChunk({ type: "text", text: text2 });
214
+ } else if (currentBlockType === "tool_use" && event.delta.type === "input_json_delta") {
215
+ const lastTool = toolUseBlocks[toolUseBlocks.length - 1];
216
+ if (lastTool) {
217
+ lastTool.inputJson += event.delta.partial_json;
218
+ }
237
219
  }
220
+ } else if (event.type === "content_block_stop") {
221
+ currentBlockType = null;
222
+ }
223
+ }
224
+ const toolUses = toolUseBlocks.map((block) => ({
225
+ id: block.id,
226
+ name: block.name,
227
+ input: block.inputJson ? JSON.parse(block.inputJson) : {}
228
+ }));
229
+ onChunk({ type: "done" });
230
+ if (toolUses.length > 0) {
231
+ const contentBlocks = [];
232
+ if (fullText) {
233
+ contentBlocks.push({ type: "text", text: fullText });
234
+ }
235
+ for (const tu of toolUses) {
236
+ contentBlocks.push({
237
+ type: "tool_use",
238
+ id: tu.id,
239
+ name: tu.name,
240
+ input: tu.input
241
+ });
238
242
  }
239
- onChunk({ type: "done" });
240
243
  return {
241
- message: { role: "assistant", content: fullText },
242
- toolUses: []
244
+ message: { role: "assistant", content: contentBlocks },
245
+ toolUses
243
246
  };
244
247
  }
248
+ return {
249
+ message: { role: "assistant", content: fullText },
250
+ toolUses: []
251
+ };
245
252
  } catch (error) {
246
253
  if (error instanceof Anthropic.AuthenticationError) {
247
254
  throw new Error(
@@ -316,74 +323,79 @@ function createOpenAIClient(apiKey, model) {
316
323
  const openaiMessages = toOpenAIMessages(systemPrompt, messages);
317
324
  const hasTools = tools && tools.length > 0;
318
325
  try {
326
+ let fullText = "";
327
+ const toolCallAccumulators = /* @__PURE__ */ new Map();
328
+ const createParams = {
329
+ model,
330
+ max_tokens: 8192,
331
+ messages: openaiMessages,
332
+ stream: true
333
+ };
319
334
  if (hasTools) {
320
- const response = await client.chat.completions.create({
321
- model,
322
- max_tokens: 8192,
323
- messages: openaiMessages,
324
- tools: tools.map((t) => ({
325
- type: "function",
326
- function: {
327
- name: t.name,
328
- description: t.description,
329
- parameters: t.input_schema
330
- }
331
- }))
332
- });
333
- const choice = response.choices[0];
334
- const textContent = choice?.message?.content || "";
335
- const toolCalls = choice?.message?.tool_calls || [];
336
- const toolUses = toolCalls.map((tc) => ({
337
- id: tc.id,
338
- name: tc.function.name,
339
- input: JSON.parse(tc.function.arguments || "{}")
335
+ createParams.tools = tools.map((t) => ({
336
+ type: "function",
337
+ function: {
338
+ name: t.name,
339
+ description: t.description,
340
+ parameters: t.input_schema
341
+ }
340
342
  }));
341
- if (textContent && toolUses.length === 0) {
342
- onChunk({ type: "text", text: textContent });
343
- onChunk({ type: "done" });
344
- } else if (textContent) {
345
- onChunk({ type: "text", text: textContent });
346
- }
347
- if (toolUses.length > 0) {
348
- const contentBlocks = [
349
- ...textContent ? [{ type: "text", text: textContent }] : [],
350
- ...toolUses.map((tu) => ({
351
- type: "tool_use",
352
- id: tu.id,
353
- name: tu.name,
354
- input: tu.input
355
- }))
356
- ];
357
- return {
358
- message: { role: "assistant", content: contentBlocks },
359
- toolUses
360
- };
343
+ }
344
+ const stream = await client.chat.completions.create(
345
+ createParams
346
+ );
347
+ for await (const chunk of stream) {
348
+ const delta = chunk.choices[0]?.delta;
349
+ if (!delta) continue;
350
+ if (delta.content) {
351
+ fullText += delta.content;
352
+ onChunk({ type: "text", text: delta.content });
361
353
  }
362
- return {
363
- message: { role: "assistant", content: textContent },
364
- toolUses: []
365
- };
366
- } else {
367
- let fullText = "";
368
- const stream = await client.chat.completions.create({
369
- model,
370
- max_tokens: 8192,
371
- messages: openaiMessages,
372
- stream: true
373
- });
374
- for await (const chunk of stream) {
375
- const text2 = chunk.choices[0]?.delta?.content || "";
376
- if (text2) {
377
- fullText += text2;
378
- onChunk({ type: "text", text: text2 });
354
+ if (delta.tool_calls) {
355
+ for (const tc of delta.tool_calls) {
356
+ const idx = tc.index;
357
+ let acc = toolCallAccumulators.get(idx);
358
+ if (!acc) {
359
+ acc = { id: "", name: "", arguments: "" };
360
+ toolCallAccumulators.set(idx, acc);
361
+ }
362
+ if (tc.id) {
363
+ acc.id = tc.id;
364
+ }
365
+ if (tc.function?.name) {
366
+ acc.name = tc.function.name;
367
+ }
368
+ if (tc.function?.arguments) {
369
+ acc.arguments += tc.function.arguments;
370
+ }
379
371
  }
380
372
  }
381
- onChunk({ type: "done" });
373
+ }
374
+ const toolUses = Array.from(toolCallAccumulators.entries()).sort(([a], [b]) => a - b).map(([, acc]) => ({
375
+ id: acc.id,
376
+ name: acc.name,
377
+ input: JSON.parse(acc.arguments || "{}")
378
+ }));
379
+ onChunk({ type: "done" });
380
+ if (toolUses.length > 0) {
381
+ const contentBlocks = [
382
+ ...fullText ? [{ type: "text", text: fullText }] : [],
383
+ ...toolUses.map((tu) => ({
384
+ type: "tool_use",
385
+ id: tu.id,
386
+ name: tu.name,
387
+ input: tu.input
388
+ }))
389
+ ];
382
390
  return {
383
- message: { role: "assistant", content: fullText },
384
- toolUses: []
391
+ message: { role: "assistant", content: contentBlocks },
392
+ toolUses
385
393
  };
386
394
  }
395
+ return {
396
+ message: { role: "assistant", content: fullText },
397
+ toolUses: []
398
+ };
387
399
  } catch (error) {
388
400
  if (error instanceof OpenAI.AuthenticationError) {
389
401
  throw new Error(
@@ -821,7 +833,7 @@ function handleHelp() {
821
833
  ` ${pc.cyan("/memory")} View recent memories [search|clear ...]`,
822
834
  ` ${pc.cyan("/status")} Ecosystem dashboard`,
823
835
  ` ${pc.cyan("/doctor")} Health check all layers`,
824
- ` ${pc.cyan("/remind")} Set a reminder`,
836
+ ` ${pc.cyan("/save")} Save conversation to memory`,
825
837
  ` ${pc.cyan("/model")} Show current LLM model`,
826
838
  ` ${pc.cyan("/update")} Check for updates`,
827
839
  ` ${pc.cyan("/reconfig")} Reset LLM config`,
@@ -830,17 +842,8 @@ function handleHelp() {
830
842
  ].join("\n")
831
843
  };
832
844
  }
833
- function handleRemind(input) {
834
- const parts = input.trim().split(/\s+/);
835
- if (parts.length < 3) {
836
- return {
837
- handled: true,
838
- output: "Usage: /remind <time> <message>\nExamples: /remind 30m Review PR, /remind 2h Deploy, /remind tomorrow Check metrics"
839
- };
840
- }
841
- const timeStr = parts[1];
842
- const message = parts.slice(2).join(" ");
843
- return { handled: true, remind: { timeStr, message } };
845
+ function handleSave() {
846
+ return { handled: true, saveConversation: true };
844
847
  }
845
848
  function handleReconfig() {
846
849
  const configPath = path4.join(os4.homedir(), ".aman-agent", "config.json");
@@ -921,8 +924,8 @@ async function handleCommand(input, ctx) {
921
924
  return handleStatusCommand(ctx);
922
925
  case "doctor":
923
926
  return handleDoctorCommand(ctx);
924
- case "remind":
925
- return handleRemind(trimmed);
927
+ case "save":
928
+ return handleSave();
926
929
  case "update-config":
927
930
  case "reconfig":
928
931
  return handleReconfig();
@@ -934,46 +937,8 @@ async function handleCommand(input, ctx) {
934
937
  }
935
938
  }
936
939
 
937
- // src/reminders.ts
938
- import pc2 from "picocolors";
939
- var activeReminders = [];
940
- function parseTime(timeStr) {
941
- const match = timeStr.match(/^(\d+)(m|h)$/);
942
- if (match) {
943
- const value = parseInt(match[1]);
944
- const unit = match[2];
945
- return unit === "m" ? value * 60 * 1e3 : value * 60 * 60 * 1e3;
946
- }
947
- if (timeStr === "tomorrow") return 24 * 60 * 60 * 1e3;
948
- return null;
949
- }
950
- function setReminder(timeStr, message) {
951
- const ms = parseTime(timeStr);
952
- if (!ms) return null;
953
- const reminder = {
954
- message,
955
- dueAt: Date.now() + ms
956
- };
957
- reminder.timer = setTimeout(() => {
958
- console.log(`
959
- ${pc2.yellow("\u23F0")} ${pc2.bold("Reminder:")} ${message}`);
960
- const idx = activeReminders.indexOf(reminder);
961
- if (idx >= 0) activeReminders.splice(idx, 1);
962
- }, ms);
963
- activeReminders.push(reminder);
964
- const mins = Math.round(ms / 6e4);
965
- if (mins < 60) return `${mins} minutes`;
966
- const hours = Math.round(mins / 60);
967
- return `${hours} hour${hours > 1 ? "s" : ""}`;
968
- }
969
- function clearReminders() {
970
- for (const r of activeReminders) {
971
- if (r.timer) clearTimeout(r.timer);
972
- }
973
- activeReminders.length = 0;
974
- }
975
-
976
940
  // src/hooks.ts
941
+ import pc2 from "picocolors";
977
942
  import * as p from "@clack/prompts";
978
943
  var isHookCall = false;
979
944
  async function onSessionStart(ctx) {
@@ -1076,33 +1041,24 @@ async function onWorkflowMatch(userInput, ctx) {
1076
1041
  isHookCall = false;
1077
1042
  }
1078
1043
  }
1079
- async function onSessionEnd(ctx, messages) {
1044
+ async function onSessionEnd(ctx, messages, sessionId) {
1080
1045
  try {
1081
- if (ctx.config.evalPrompt) {
1082
- const rating = await p.select({
1083
- message: "Quick rating for this session?",
1084
- options: [
1085
- { value: "great", label: "Great" },
1086
- { value: "good", label: "Good" },
1087
- { value: "okay", label: "Okay" },
1088
- { value: "skip", label: "Skip" }
1089
- ],
1090
- initialValue: "skip"
1091
- });
1092
- if (!p.isCancel(rating) && rating !== "skip") {
1046
+ if (ctx.config.autoSessionSave && messages.length > 2) {
1047
+ console.log(pc2.dim("\n Saving conversation to memory..."));
1048
+ const textMessages = messages.filter((m) => typeof m.content === "string").slice(-50);
1049
+ for (const msg of textMessages) {
1093
1050
  try {
1094
1051
  isHookCall = true;
1095
- await ctx.mcpManager.callTool("eval_log", {
1096
- rating,
1097
- highlights: "Quick session rating",
1098
- improvements: ""
1052
+ await ctx.mcpManager.callTool("memory_log", {
1053
+ session_id: sessionId,
1054
+ role: msg.role,
1055
+ content: msg.content.slice(0, 5e3)
1099
1056
  });
1057
+ } catch {
1100
1058
  } finally {
1101
1059
  isHookCall = false;
1102
1060
  }
1103
1061
  }
1104
- }
1105
- if (ctx.config.autoSessionSave && messages.length > 2) {
1106
1062
  let lastUserMsg = "";
1107
1063
  for (let i = messages.length - 1; i >= 0; i--) {
1108
1064
  if (messages[i].role === "user" && typeof messages[i].content === "string") {
@@ -1122,14 +1078,97 @@ async function onSessionEnd(ctx, messages) {
1122
1078
  isHookCall = false;
1123
1079
  }
1124
1080
  }
1081
+ console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
1082
+ }
1083
+ if (ctx.config.evalPrompt) {
1084
+ const rating = await p.select({
1085
+ message: "Quick rating for this session?",
1086
+ options: [
1087
+ { value: "great", label: "Great" },
1088
+ { value: "good", label: "Good" },
1089
+ { value: "okay", label: "Okay" },
1090
+ { value: "skip", label: "Skip" }
1091
+ ],
1092
+ initialValue: "skip"
1093
+ });
1094
+ if (!p.isCancel(rating) && rating !== "skip") {
1095
+ try {
1096
+ isHookCall = true;
1097
+ await ctx.mcpManager.callTool("eval_log", {
1098
+ rating,
1099
+ highlights: "Quick session rating",
1100
+ improvements: ""
1101
+ });
1102
+ } finally {
1103
+ isHookCall = false;
1104
+ }
1105
+ }
1125
1106
  }
1126
1107
  } catch {
1127
1108
  }
1128
1109
  }
1129
1110
 
1111
+ // src/context-manager.ts
1112
+ function estimateMessageTokens(msg) {
1113
+ if (typeof msg.content === "string") {
1114
+ return Math.round(msg.content.split(/\s+/).filter(Boolean).length * 1.3);
1115
+ }
1116
+ let text2 = "";
1117
+ for (const block of msg.content) {
1118
+ if (block.type === "text") text2 += block.text;
1119
+ else if (block.type === "tool_result") text2 += block.content;
1120
+ else if (block.type === "tool_use") text2 += JSON.stringify(block.input);
1121
+ }
1122
+ return Math.round(text2.split(/\s+/).filter(Boolean).length * 1.3);
1123
+ }
1124
+ function estimateTotalTokens(messages) {
1125
+ let total = 0;
1126
+ for (const msg of messages) {
1127
+ total += estimateMessageTokens(msg);
1128
+ }
1129
+ return total;
1130
+ }
1131
+ var MAX_CONVERSATION_TOKENS = 8e4;
1132
+ var KEEP_RECENT = 10;
1133
+ var KEEP_INITIAL = 2;
1134
+ function trimConversation(messages, _client) {
1135
+ const totalTokens = estimateTotalTokens(messages);
1136
+ if (totalTokens < MAX_CONVERSATION_TOKENS || messages.length <= KEEP_INITIAL + KEEP_RECENT) {
1137
+ return;
1138
+ }
1139
+ const initial = messages.slice(0, KEEP_INITIAL);
1140
+ const recent = messages.slice(-KEEP_RECENT);
1141
+ const middle = messages.slice(KEEP_INITIAL, messages.length - KEEP_RECENT);
1142
+ const summaryParts = [];
1143
+ for (const msg of middle) {
1144
+ if (typeof msg.content === "string" && msg.content.length > 0) {
1145
+ const preview = msg.content.slice(0, 150);
1146
+ summaryParts.push(`[${msg.role}]: ${preview}${msg.content.length > 150 ? "..." : ""}`);
1147
+ }
1148
+ }
1149
+ const summaryText = `<conversation-summary>
1150
+ The following is a summary of ${middle.length} earlier messages that were compressed to save context:
1151
+
1152
+ ${summaryParts.slice(0, 20).join("\n")}
1153
+ ${summaryParts.length > 20 ? `
1154
+ ... and ${summaryParts.length - 20} more messages` : ""}
1155
+ </conversation-summary>`;
1156
+ messages.length = 0;
1157
+ messages.push(...initial);
1158
+ messages.push({ role: "user", content: summaryText });
1159
+ messages.push({ role: "assistant", content: "I have the context from our earlier conversation. Let's continue." });
1160
+ messages.push(...recent);
1161
+ }
1162
+
1130
1163
  // src/agent.ts
1164
+ function generateSessionId() {
1165
+ const now = /* @__PURE__ */ new Date();
1166
+ const pad = (n) => n.toString().padStart(2, "0");
1167
+ return `session-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
1168
+ }
1131
1169
  async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager, hooksConfig) {
1132
1170
  const messages = [];
1171
+ const sessionId = generateSessionId();
1133
1172
  const rl = readline.createInterface({
1134
1173
  input: process.stdin,
1135
1174
  output: process.stdout
@@ -1138,7 +1177,7 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
1138
1177
  if (mcpManager && hooksConfig) {
1139
1178
  try {
1140
1179
  const hookCtx = { mcpManager, config: hooksConfig };
1141
- await onSessionEnd(hookCtx, messages);
1180
+ await onSessionEnd(hookCtx, messages, sessionId);
1142
1181
  } catch {
1143
1182
  }
1144
1183
  }
@@ -1176,11 +1215,10 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
1176
1215
  const cmdResult = await handleCommand(input, { model, mcpManager });
1177
1216
  if (cmdResult.handled) {
1178
1217
  if (cmdResult.quit) {
1179
- clearReminders();
1180
1218
  if (mcpManager && hooksConfig) {
1181
1219
  try {
1182
1220
  const hookCtx = { mcpManager, config: hooksConfig };
1183
- await onSessionEnd(hookCtx, messages);
1221
+ await onSessionEnd(hookCtx, messages, sessionId);
1184
1222
  } catch {
1185
1223
  }
1186
1224
  }
@@ -1188,17 +1226,12 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
1188
1226
  rl.close();
1189
1227
  return;
1190
1228
  }
1191
- if (cmdResult.remind) {
1192
- const duration = setReminder(
1193
- cmdResult.remind.timeStr,
1194
- cmdResult.remind.message
1195
- );
1196
- if (duration) {
1197
- console.log(pc3.dim(`Reminder set for ${duration} from now.`));
1198
- } else {
1199
- console.log(
1200
- pc3.red("Invalid time format. Use: 5m, 30m, 1h, 2h, tomorrow")
1201
- );
1229
+ if (cmdResult.saveConversation && mcpManager) {
1230
+ try {
1231
+ await saveConversationToMemory(mcpManager, messages, sessionId);
1232
+ console.log(pc3.green("Conversation saved to memory."));
1233
+ } catch {
1234
+ console.log(pc3.red("Failed to save conversation."));
1202
1235
  }
1203
1236
  continue;
1204
1237
  }
@@ -1231,6 +1264,7 @@ ${wfMatch.steps}
1231
1264
  } catch {
1232
1265
  }
1233
1266
  }
1267
+ trimConversation(messages, client);
1234
1268
  messages.push({ role: "user", content: input });
1235
1269
  process.stdout.write(pc3.cyan(`
1236
1270
  ${aiName} > `));
@@ -1308,127 +1342,28 @@ Error: ${message}`));
1308
1342
  }
1309
1343
  }
1310
1344
  }
1311
-
1312
- // src/scheduler.ts
1313
- import fs5 from "fs";
1314
- import path5 from "path";
1315
- import os5 from "os";
1316
- var SCHEDULES_PATH = path5.join(os5.homedir(), ".aman-agent", "schedules.json");
1317
- function loadSchedules() {
1318
- if (!fs5.existsSync(SCHEDULES_PATH)) return [];
1319
- try {
1320
- return JSON.parse(fs5.readFileSync(SCHEDULES_PATH, "utf-8"));
1321
- } catch {
1322
- return [];
1323
- }
1324
- }
1325
- function saveSchedules(tasks) {
1326
- const dir = path5.dirname(SCHEDULES_PATH);
1327
- fs5.mkdirSync(dir, { recursive: true });
1328
- fs5.writeFileSync(
1329
- SCHEDULES_PATH,
1330
- JSON.stringify(tasks, null, 2) + "\n",
1331
- "utf-8"
1332
- );
1333
- }
1334
- function addSchedule(task) {
1335
- const tasks = loadSchedules();
1336
- const newTask = {
1337
- ...task,
1338
- id: Date.now().toString(36),
1339
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1340
- };
1341
- tasks.push(newTask);
1342
- saveSchedules(tasks);
1343
- return newTask;
1344
- }
1345
- function removeSchedule(id) {
1346
- const tasks = loadSchedules();
1347
- const filtered = tasks.filter((t) => t.id !== id);
1348
- if (filtered.length === tasks.length) return false;
1349
- saveSchedules(filtered);
1350
- return true;
1351
- }
1352
- function getDueTasks() {
1353
- const tasks = loadSchedules();
1354
- const now = /* @__PURE__ */ new Date();
1355
- return tasks.filter((task) => {
1356
- if (!task.lastRun) return true;
1357
- const lastRun = new Date(task.lastRun);
1358
- return isDue(task.schedule, lastRun, now);
1359
- });
1360
- }
1361
- function isDue(schedule, lastRun, now) {
1362
- const hoursSinceLastRun = (now.getTime() - lastRun.getTime()) / (1e3 * 60 * 60);
1363
- if (schedule.startsWith("every ")) {
1364
- const match = schedule.match(/every (\d+)h/);
1365
- if (match) return hoursSinceLastRun >= parseInt(match[1]);
1366
- }
1367
- if (schedule === "daily" || schedule.startsWith("daily ")) {
1368
- return hoursSinceLastRun >= 20;
1369
- }
1370
- if (schedule === "weekdays" || schedule.startsWith("weekdays ")) {
1371
- const day = now.getDay();
1372
- return day >= 1 && day <= 5 && hoursSinceLastRun >= 20;
1373
- }
1374
- if (schedule.startsWith("weekly")) {
1375
- return hoursSinceLastRun >= 144;
1376
- }
1377
- return false;
1378
- }
1379
-
1380
- // src/notifications.ts
1381
- import fs6 from "fs";
1382
- import path6 from "path";
1383
- import os6 from "os";
1384
- import pc4 from "picocolors";
1385
- function checkNotifications() {
1386
- const notifications = [];
1387
- const dueTasks = getDueTasks();
1388
- for (const task of dueTasks) {
1389
- notifications.push({
1390
- type: "schedule",
1391
- message: `${task.name} (${task.schedule})`
1392
- });
1393
- }
1394
- const evalPath = path6.join(os6.homedir(), ".aeval", "eval.md");
1395
- if (fs6.existsSync(evalPath)) {
1396
- const content = fs6.readFileSync(evalPath, "utf-8");
1397
- const dateMatch = content.match(/- Last updated: (.+)$/m);
1398
- if (dateMatch) {
1399
- const lastDate = new Date(dateMatch[1]);
1400
- const daysSince = (Date.now() - lastDate.getTime()) / (1e3 * 60 * 60 * 24);
1401
- if (daysSince > 3) {
1402
- notifications.push({
1403
- type: "eval",
1404
- message: `No session logged in ${Math.floor(daysSince)} days \u2014 run /eval to log one`
1405
- });
1406
- }
1345
+ async function saveConversationToMemory(mcpManager, messages, sessionId) {
1346
+ const recentMessages = messages.slice(-50);
1347
+ for (const msg of recentMessages) {
1348
+ if (typeof msg.content !== "string") continue;
1349
+ try {
1350
+ await mcpManager.callTool("memory_log", {
1351
+ session_id: sessionId,
1352
+ role: msg.role,
1353
+ content: msg.content.slice(0, 5e3)
1354
+ });
1355
+ } catch {
1407
1356
  }
1408
1357
  }
1409
- return notifications;
1410
- }
1411
- function displayNotifications(notifications) {
1412
- if (notifications.length === 0) return;
1413
- console.log(
1414
- pc4.yellow(
1415
- `
1416
- \u26A0 ${notifications.length} notification${notifications.length > 1 ? "s" : ""}:`
1417
- )
1418
- );
1419
- for (const n of notifications) {
1420
- console.log(` - ${n.message}`);
1421
- }
1422
- console.log("");
1423
1358
  }
1424
1359
 
1425
1360
  // src/index.ts
1426
- import fs7 from "fs";
1427
- import path7 from "path";
1428
- import os7 from "os";
1361
+ import fs5 from "fs";
1362
+ import path5 from "path";
1363
+ import os5 from "os";
1429
1364
  var program = new Command();
1430
1365
  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) => {
1431
- p2.intro(pc5.bold("aman agent") + pc5.dim(" \u2014 starting your AI companion"));
1366
+ p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 starting your AI companion"));
1432
1367
  let config = loadConfig();
1433
1368
  if (!config) {
1434
1369
  p2.log.info("First-time setup \u2014 configure your LLM connection.");
@@ -1459,7 +1394,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1459
1394
  defaultModel = modelInput || "llama3.2";
1460
1395
  } else if (provider === "anthropic") {
1461
1396
  p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
1462
- p2.log.info(pc5.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
1397
+ p2.log.info(pc4.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
1463
1398
  apiKey = await p2.text({
1464
1399
  message: "API key (starts with sk-ant-)",
1465
1400
  validate: (v) => v.length === 0 ? "API key is required" : void 0
@@ -1525,28 +1460,26 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1525
1460
  const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
1526
1461
  if (layers.length === 0) {
1527
1462
  p2.log.warning(
1528
- "No ecosystem configured. Run " + pc5.bold("npx @aman_asmuei/aman") + " first."
1463
+ "No ecosystem configured. Run " + pc4.bold("npx @aman_asmuei/aman") + " first."
1529
1464
  );
1530
1465
  p2.log.info("Starting with empty system prompt.");
1531
1466
  } else {
1532
1467
  p2.log.success(
1533
- `Loaded: ${layers.join(", ")} ${pc5.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
1468
+ `Loaded: ${layers.join(", ")} ${pc4.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
1534
1469
  );
1535
1470
  if (truncated.length > 0) {
1536
- p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc5.dim("(over budget)")}`);
1471
+ p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc4.dim("(over budget)")}`);
1537
1472
  }
1538
1473
  }
1539
- p2.log.info(`Model: ${pc5.dim(model)}`);
1540
- const corePath = path7.join(os7.homedir(), ".acore", "core.md");
1474
+ p2.log.info(`Model: ${pc4.dim(model)}`);
1475
+ const corePath = path5.join(os5.homedir(), ".acore", "core.md");
1541
1476
  let aiName = "Assistant";
1542
- if (fs7.existsSync(corePath)) {
1543
- const content = fs7.readFileSync(corePath, "utf-8");
1477
+ if (fs5.existsSync(corePath)) {
1478
+ const content = fs5.readFileSync(corePath, "utf-8");
1544
1479
  const match = content.match(/^# (.+)$/m);
1545
1480
  if (match) aiName = match[1];
1546
1481
  }
1547
- p2.log.success(`${pc5.bold(aiName)} is ready.`);
1548
- const notifications = checkNotifications();
1549
- displayNotifications(notifications);
1482
+ p2.log.success(`${pc4.bold(aiName)} is ready.`);
1550
1483
  const mcpManager = new McpManager();
1551
1484
  p2.log.step("Connecting to MCP servers...");
1552
1485
  await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
@@ -1583,85 +1516,5 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
1583
1516
  );
1584
1517
  await mcpManager.disconnect();
1585
1518
  });
1586
- program.command("schedule").description("Manage scheduled tasks").argument("[action]", "add, list, or remove").argument("[id]", "task ID (for remove)").action(async (action, id) => {
1587
- if (!action || action === "list") {
1588
- const tasks = loadSchedules();
1589
- if (tasks.length === 0) {
1590
- console.log(pc5.dim("No scheduled tasks."));
1591
- return;
1592
- }
1593
- console.log(pc5.bold("Scheduled tasks:\n"));
1594
- for (const task of tasks) {
1595
- const lastRun = task.lastRun ? pc5.dim(` (last run: ${new Date(task.lastRun).toLocaleString()})`) : pc5.dim(" (never run)");
1596
- console.log(
1597
- ` ${pc5.cyan(task.id)} ${task.name} ${pc5.dim(task.schedule)} [${task.mode}]${lastRun}`
1598
- );
1599
- }
1600
- return;
1601
- }
1602
- if (action === "add") {
1603
- const name = await p2.text({
1604
- message: "Task name?",
1605
- validate: (v) => v.length === 0 ? "Name is required" : void 0
1606
- });
1607
- if (p2.isCancel(name)) return;
1608
- const schedule = await p2.select({
1609
- message: "Schedule?",
1610
- options: [
1611
- { value: "daily 9am", label: "Daily at 9am" },
1612
- { value: "weekdays 9am", label: "Weekdays at 9am" },
1613
- { value: "weekly friday 4pm", label: "Weekly Friday 4pm" },
1614
- { value: "every 2h", label: "Every 2 hours" },
1615
- { value: "every 4h", label: "Every 4 hours" }
1616
- ]
1617
- });
1618
- if (p2.isCancel(schedule)) return;
1619
- const actionType = await p2.select({
1620
- message: "What should happen?",
1621
- options: [
1622
- { value: "notify", label: "Show notification" },
1623
- { value: "auto-run", label: "Run automatically" }
1624
- ]
1625
- });
1626
- if (p2.isCancel(actionType)) return;
1627
- let taskAction = "notify";
1628
- if (actionType === "auto-run") {
1629
- const cmd = await p2.text({
1630
- message: "Command to run?",
1631
- placeholder: "e.g. run:daily-standup",
1632
- validate: (v) => v.length === 0 ? "Command is required" : void 0
1633
- });
1634
- if (p2.isCancel(cmd)) return;
1635
- taskAction = cmd;
1636
- }
1637
- const task = addSchedule({
1638
- name,
1639
- schedule,
1640
- action: taskAction,
1641
- mode: actionType
1642
- });
1643
- console.log(
1644
- pc5.green(`
1645
- Scheduled task created: ${pc5.bold(task.name)} (${task.id})`)
1646
- );
1647
- return;
1648
- }
1649
- if (action === "remove") {
1650
- if (!id) {
1651
- console.log(pc5.red("Usage: aman-agent schedule remove <id>"));
1652
- return;
1653
- }
1654
- const removed = removeSchedule(id);
1655
- if (removed) {
1656
- console.log(pc5.green(`Task ${id} removed.`));
1657
- } else {
1658
- console.log(pc5.red(`Task ${id} not found.`));
1659
- }
1660
- return;
1661
- }
1662
- console.log(
1663
- pc5.red(`Unknown action: ${action}. Use add, list, or remove.`)
1664
- );
1665
- });
1666
1519
  program.parse();
1667
1520
  //# sourceMappingURL=index.js.map