@aman_asmuei/aman-agent 0.2.4 → 0.3.1
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 +681 -181
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import * as
|
|
3
|
+
import * as p2 from "@clack/prompts";
|
|
4
4
|
import pc5 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import os from "os";
|
|
10
|
+
var DEFAULT_HOOKS = {
|
|
11
|
+
memoryRecall: true,
|
|
12
|
+
sessionResume: true,
|
|
13
|
+
rulesCheck: true,
|
|
14
|
+
workflowSuggest: true,
|
|
15
|
+
evalPrompt: true,
|
|
16
|
+
autoSessionSave: true
|
|
17
|
+
};
|
|
10
18
|
var CONFIG_DIR = path.join(os.homedir(), ".aman-agent");
|
|
11
19
|
var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
12
20
|
function loadConfig() {
|
|
13
21
|
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
14
22
|
try {
|
|
15
|
-
|
|
23
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
24
|
+
raw.hooks = { ...DEFAULT_HOOKS, ...raw.hooks };
|
|
25
|
+
return raw;
|
|
16
26
|
} catch {
|
|
17
27
|
return null;
|
|
18
28
|
}
|
|
@@ -91,10 +101,10 @@ var ECOSYSTEM_FILES = [
|
|
|
91
101
|
{ name: "skills", dir: ".askill", file: "skills.md" }
|
|
92
102
|
];
|
|
93
103
|
function assembleSystemPrompt(maxTokens) {
|
|
94
|
-
const
|
|
104
|
+
const home2 = os2.homedir();
|
|
95
105
|
const components = [];
|
|
96
106
|
for (const entry of ECOSYSTEM_FILES) {
|
|
97
|
-
const filePath = path2.join(
|
|
107
|
+
const filePath = path2.join(home2, entry.dir, entry.file);
|
|
98
108
|
if (fs2.existsSync(filePath)) {
|
|
99
109
|
const content = fs2.readFileSync(filePath, "utf-8").trim();
|
|
100
110
|
components.push({
|
|
@@ -503,152 +513,425 @@ import * as readline from "readline";
|
|
|
503
513
|
import pc3 from "picocolors";
|
|
504
514
|
|
|
505
515
|
// src/commands.ts
|
|
516
|
+
import fs4 from "fs";
|
|
517
|
+
import path4 from "path";
|
|
518
|
+
import os4 from "os";
|
|
519
|
+
import { execFileSync } from "child_process";
|
|
520
|
+
import pc from "picocolors";
|
|
521
|
+
|
|
522
|
+
// src/layers/parsers.ts
|
|
506
523
|
import fs3 from "fs";
|
|
507
524
|
import path3 from "path";
|
|
508
525
|
import os3 from "os";
|
|
509
|
-
|
|
510
|
-
|
|
526
|
+
var home = os3.homedir();
|
|
527
|
+
var LAYER_FILES = [
|
|
528
|
+
{ name: "identity", dir: ".acore", file: "core.md" },
|
|
529
|
+
{ name: "rules", dir: ".arules", file: "rules.md" },
|
|
530
|
+
{ name: "workflows", dir: ".aflow", file: "flow.md" },
|
|
531
|
+
{ name: "tools", dir: ".akit", file: "kit.md" },
|
|
532
|
+
{ name: "skills", dir: ".askill", file: "skills.md" },
|
|
533
|
+
{ name: "eval", dir: ".aeval", file: "eval.md" }
|
|
534
|
+
];
|
|
535
|
+
function countLines(content, pattern) {
|
|
536
|
+
return (content.match(pattern) || []).length;
|
|
537
|
+
}
|
|
538
|
+
function getLayerSummary(name, content) {
|
|
539
|
+
switch (name) {
|
|
540
|
+
case "identity": {
|
|
541
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
542
|
+
return nameMatch ? nameMatch[1] : "configured";
|
|
543
|
+
}
|
|
544
|
+
case "rules":
|
|
545
|
+
return `${countLines(content, /^- /gm)} rules`;
|
|
546
|
+
case "workflows":
|
|
547
|
+
return `${countLines(content, /^## /gm)} workflows`;
|
|
548
|
+
case "tools":
|
|
549
|
+
return `${countLines(content, /^- \*\*/gm)} tools`;
|
|
550
|
+
case "skills":
|
|
551
|
+
return `${countLines(content, /^### /gm)} skills`;
|
|
552
|
+
case "eval": {
|
|
553
|
+
const sessions = countLines(content, /^### Session/gm);
|
|
554
|
+
return `${sessions} sessions logged`;
|
|
555
|
+
}
|
|
556
|
+
default:
|
|
557
|
+
return "unknown";
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function getEcosystemStatus(mcpToolCount, amemConnected) {
|
|
561
|
+
const layers = LAYER_FILES.map((entry) => {
|
|
562
|
+
const filePath = path3.join(home, entry.dir, entry.file);
|
|
563
|
+
const exists = fs3.existsSync(filePath);
|
|
564
|
+
let summary = "not configured";
|
|
565
|
+
if (exists) {
|
|
566
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
567
|
+
summary = getLayerSummary(entry.name, content);
|
|
568
|
+
}
|
|
569
|
+
return { name: entry.name, exists, path: filePath, summary };
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
layers,
|
|
573
|
+
mcpConnected: mcpToolCount > 0,
|
|
574
|
+
mcpToolCount,
|
|
575
|
+
amemConnected
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/commands.ts
|
|
511
580
|
function readEcosystemFile(filePath, label) {
|
|
512
|
-
if (!
|
|
581
|
+
if (!fs4.existsSync(filePath)) {
|
|
513
582
|
return pc.dim(`No ${label} file found at ${filePath}`);
|
|
514
583
|
}
|
|
515
|
-
return
|
|
584
|
+
return fs4.readFileSync(filePath, "utf-8").trim();
|
|
585
|
+
}
|
|
586
|
+
function parseCommand(input) {
|
|
587
|
+
const trimmed = input.trim();
|
|
588
|
+
const parts = trimmed.split(/\s+/);
|
|
589
|
+
const base = parts[0].toLowerCase().replace(/^\//, "");
|
|
590
|
+
const action = parts.length > 1 ? parts[1].toLowerCase() : void 0;
|
|
591
|
+
const args = parts.slice(2);
|
|
592
|
+
return { base, action, args };
|
|
516
593
|
}
|
|
517
|
-
function
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (cmd === "/quit" || cmd === "/exit" || cmd === "/q") {
|
|
521
|
-
return { handled: true, quit: true };
|
|
594
|
+
async function mcpWrite(ctx, layer, tool, args) {
|
|
595
|
+
if (!ctx.mcpManager) {
|
|
596
|
+
return pc.red(`Cannot modify ${layer}: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp`);
|
|
522
597
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
output: [
|
|
527
|
-
pc.bold("Commands:"),
|
|
528
|
-
` ${pc.cyan("/help")} Show this help`,
|
|
529
|
-
` ${pc.cyan("/identity")} View your AI identity`,
|
|
530
|
-
` ${pc.cyan("/tools")} View installed tools`,
|
|
531
|
-
` ${pc.cyan("/workflows")} View defined workflows`,
|
|
532
|
-
` ${pc.cyan("/rules")} View guardrails`,
|
|
533
|
-
` ${pc.cyan("/skills")} View installed skills`,
|
|
534
|
-
` ${pc.cyan("/remind")} Set a reminder (e.g. /remind 30m Review PR)`,
|
|
535
|
-
` ${pc.cyan("/model")} Show current LLM model`,
|
|
536
|
-
` ${pc.cyan("/update")} Check for updates`,
|
|
537
|
-
` ${pc.cyan("/reconfig")} Reset LLM config (provider, model, API key)`,
|
|
538
|
-
` ${pc.cyan("/clear")} Clear conversation history`,
|
|
539
|
-
` ${pc.cyan("/quit")} Exit`
|
|
540
|
-
].join("\n")
|
|
541
|
-
};
|
|
598
|
+
const result = await ctx.mcpManager.callTool(tool, args);
|
|
599
|
+
if (result.startsWith("Error")) {
|
|
600
|
+
return pc.red(result);
|
|
542
601
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
602
|
+
return pc.green(result);
|
|
603
|
+
}
|
|
604
|
+
async function handleIdentityCommand(action, args, ctx) {
|
|
605
|
+
const home2 = os4.homedir();
|
|
606
|
+
if (!action) {
|
|
607
|
+
const content = readEcosystemFile(path4.join(home2, ".acore", "core.md"), "identity (acore)");
|
|
548
608
|
return { handled: true, output: content };
|
|
549
609
|
}
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
610
|
+
if (action === "update") {
|
|
611
|
+
if (args.length === 0) {
|
|
612
|
+
return {
|
|
613
|
+
handled: true,
|
|
614
|
+
output: pc.yellow("Usage: /identity update <section>\nTip: describe changes in natural language and the AI will update via MCP.")
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const section = args[0];
|
|
618
|
+
const content = args.slice(1).join(" ");
|
|
619
|
+
if (!content) {
|
|
620
|
+
return {
|
|
621
|
+
handled: true,
|
|
622
|
+
output: pc.yellow("Usage: /identity update <section> <new content...>\nExample: /identity update Personality Warm, curious, and direct.")
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const output = await mcpWrite(ctx, "identity", "identity_update_section", { section, content });
|
|
626
|
+
return { handled: true, output };
|
|
627
|
+
}
|
|
628
|
+
return { handled: true, output: pc.yellow(`Unknown action: /identity ${action}. Use /identity or /identity update <section>.`) };
|
|
629
|
+
}
|
|
630
|
+
async function handleRulesCommand(action, args, ctx) {
|
|
631
|
+
const home2 = os4.homedir();
|
|
632
|
+
if (!action) {
|
|
633
|
+
const content = readEcosystemFile(path4.join(home2, ".arules", "rules.md"), "guardrails (arules)");
|
|
555
634
|
return { handled: true, output: content };
|
|
556
635
|
}
|
|
557
|
-
if (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
636
|
+
if (action === "add") {
|
|
637
|
+
if (args.length < 2) {
|
|
638
|
+
return { handled: true, output: pc.yellow("Usage: /rules add <category> <rule text...>") };
|
|
639
|
+
}
|
|
640
|
+
const category = args[0];
|
|
641
|
+
const rule = args.slice(1).join(" ");
|
|
642
|
+
const output = await mcpWrite(ctx, "rules", "rules_add", { category, rule });
|
|
643
|
+
return { handled: true, output };
|
|
644
|
+
}
|
|
645
|
+
if (action === "remove") {
|
|
646
|
+
if (args.length < 2) {
|
|
647
|
+
return { handled: true, output: pc.yellow("Usage: /rules remove <category> <index>") };
|
|
648
|
+
}
|
|
649
|
+
const output = await mcpWrite(ctx, "rules", "rules_remove", { category: args[0], index: parseInt(args[1], 10) });
|
|
650
|
+
return { handled: true, output };
|
|
651
|
+
}
|
|
652
|
+
if (action === "toggle") {
|
|
653
|
+
if (args.length < 2) {
|
|
654
|
+
return { handled: true, output: pc.yellow("Usage: /rules toggle <category> <index>") };
|
|
655
|
+
}
|
|
656
|
+
const output = await mcpWrite(ctx, "rules", "rules_toggle", { category: args[0], index: parseInt(args[1], 10) });
|
|
657
|
+
return { handled: true, output };
|
|
658
|
+
}
|
|
659
|
+
return { handled: true, output: pc.yellow(`Unknown action: /rules ${action}. Use /rules [add|remove|toggle].`) };
|
|
660
|
+
}
|
|
661
|
+
async function handleWorkflowsCommand(action, args, ctx) {
|
|
662
|
+
const home2 = os4.homedir();
|
|
663
|
+
if (!action) {
|
|
664
|
+
const content = readEcosystemFile(path4.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
562
665
|
return { handled: true, output: content };
|
|
563
666
|
}
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
);
|
|
667
|
+
if (action === "add") {
|
|
668
|
+
if (args.length < 1) {
|
|
669
|
+
return { handled: true, output: pc.yellow("Usage: /workflows add <name>") };
|
|
670
|
+
}
|
|
671
|
+
const output = await mcpWrite(ctx, "workflows", "workflow_add", { name: args.join(" ") });
|
|
672
|
+
return { handled: true, output };
|
|
673
|
+
}
|
|
674
|
+
if (action === "remove") {
|
|
675
|
+
if (args.length < 1) {
|
|
676
|
+
return { handled: true, output: pc.yellow("Usage: /workflows remove <name>") };
|
|
677
|
+
}
|
|
678
|
+
const output = await mcpWrite(ctx, "workflows", "workflow_remove", { name: args.join(" ") });
|
|
679
|
+
return { handled: true, output };
|
|
680
|
+
}
|
|
681
|
+
return { handled: true, output: pc.yellow(`Unknown action: /workflows ${action}. Use /workflows [add|remove].`) };
|
|
682
|
+
}
|
|
683
|
+
async function handleToolsCommand(action, args, ctx) {
|
|
684
|
+
const home2 = os4.homedir();
|
|
685
|
+
if (!action) {
|
|
686
|
+
const content = readEcosystemFile(path4.join(home2, ".akit", "kit.md"), "tools (akit)");
|
|
569
687
|
return { handled: true, output: content };
|
|
570
688
|
}
|
|
571
|
-
if (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
689
|
+
if (action === "add") {
|
|
690
|
+
if (args.length < 3) {
|
|
691
|
+
return { handled: true, output: pc.yellow("Usage: /tools add <name> <type> <description...>") };
|
|
692
|
+
}
|
|
693
|
+
const name = args[0];
|
|
694
|
+
const type = args[1];
|
|
695
|
+
const description = args.slice(2).join(" ");
|
|
696
|
+
const output = await mcpWrite(ctx, "tools", "tools_add", { name, type, description });
|
|
697
|
+
return { handled: true, output };
|
|
698
|
+
}
|
|
699
|
+
if (action === "remove") {
|
|
700
|
+
if (args.length < 1) {
|
|
701
|
+
return { handled: true, output: pc.yellow("Usage: /tools remove <name>") };
|
|
702
|
+
}
|
|
703
|
+
const output = await mcpWrite(ctx, "tools", "tools_remove", { name: args.join(" ") });
|
|
704
|
+
return { handled: true, output };
|
|
705
|
+
}
|
|
706
|
+
return { handled: true, output: pc.yellow(`Unknown action: /tools ${action}. Use /tools [add|remove].`) };
|
|
707
|
+
}
|
|
708
|
+
async function handleSkillsCommand(action, args, ctx) {
|
|
709
|
+
const home2 = os4.homedir();
|
|
710
|
+
if (!action) {
|
|
711
|
+
const content = readEcosystemFile(path4.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
576
712
|
return { handled: true, output: content };
|
|
577
713
|
}
|
|
578
|
-
if (
|
|
579
|
-
|
|
580
|
-
handled: true,
|
|
581
|
-
|
|
582
|
-
};
|
|
714
|
+
if (action === "install") {
|
|
715
|
+
if (args.length < 1) {
|
|
716
|
+
return { handled: true, output: pc.yellow("Usage: /skills install <name>") };
|
|
717
|
+
}
|
|
718
|
+
const output = await mcpWrite(ctx, "skills", "skill_install", { name: args.join(" ") });
|
|
719
|
+
return { handled: true, output };
|
|
583
720
|
}
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
fs3.unlinkSync(configPath);
|
|
721
|
+
if (action === "uninstall") {
|
|
722
|
+
if (args.length < 1) {
|
|
723
|
+
return { handled: true, output: pc.yellow("Usage: /skills uninstall <name>") };
|
|
588
724
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
quit: true,
|
|
592
|
-
output: [
|
|
593
|
-
pc.green("Config reset."),
|
|
594
|
-
`Run ${pc.bold("npx @aman_asmuei/aman-agent")} again to reconfigure your LLM provider, model, and API key.`
|
|
595
|
-
].join("\n")
|
|
596
|
-
};
|
|
725
|
+
const output = await mcpWrite(ctx, "skills", "skill_uninstall", { name: args.join(" ") });
|
|
726
|
+
return { handled: true, output };
|
|
597
727
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
728
|
+
return { handled: true, output: pc.yellow(`Unknown action: /skills ${action}. Use /skills [install|uninstall].`) };
|
|
729
|
+
}
|
|
730
|
+
async function handleEvalCommand(action, args, ctx) {
|
|
731
|
+
const home2 = os4.homedir();
|
|
732
|
+
if (!action) {
|
|
733
|
+
const content = readEcosystemFile(path4.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
734
|
+
return { handled: true, output: content };
|
|
735
|
+
}
|
|
736
|
+
if (action === "milestone") {
|
|
737
|
+
if (args.length < 1) {
|
|
738
|
+
return { handled: true, output: pc.yellow("Usage: /eval milestone <text...>") };
|
|
739
|
+
}
|
|
740
|
+
const text2 = args.join(" ");
|
|
741
|
+
const output = await mcpWrite(ctx, "eval", "eval_milestone", { text: text2 });
|
|
742
|
+
return { handled: true, output };
|
|
743
|
+
}
|
|
744
|
+
return { handled: true, output: pc.yellow(`Unknown action: /eval ${action}. Use /eval or /eval milestone <text>.`) };
|
|
745
|
+
}
|
|
746
|
+
async function handleMemoryCommand(action, args, ctx) {
|
|
747
|
+
if (!action) {
|
|
748
|
+
if (!ctx.mcpManager) {
|
|
618
749
|
return {
|
|
619
750
|
handled: true,
|
|
620
|
-
output:
|
|
621
|
-
`To update, run in your terminal:`,
|
|
622
|
-
` ${pc.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
623
|
-
"",
|
|
624
|
-
`Or use npx (always latest):`,
|
|
625
|
-
` ${pc.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
626
|
-
].join("\n")
|
|
751
|
+
output: pc.red("Memory not available: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp")
|
|
627
752
|
};
|
|
628
753
|
}
|
|
754
|
+
const result = await ctx.mcpManager.callTool("memory_context", {});
|
|
755
|
+
if (result.startsWith("Error")) {
|
|
756
|
+
return { handled: true, output: pc.red(result) };
|
|
757
|
+
}
|
|
758
|
+
return { handled: true, output: result };
|
|
629
759
|
}
|
|
630
|
-
if (
|
|
631
|
-
|
|
760
|
+
if (action === "search") {
|
|
761
|
+
if (args.length < 1) {
|
|
762
|
+
return { handled: true, output: pc.yellow("Usage: /memory search <query...>") };
|
|
763
|
+
}
|
|
764
|
+
const query = args.join(" ");
|
|
765
|
+
const output = await mcpWrite(ctx, "memory", "memory_recall", { query });
|
|
766
|
+
return { handled: true, output };
|
|
632
767
|
}
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
return {
|
|
637
|
-
handled: true,
|
|
638
|
-
output: "Usage: /remind <time> <message>\nExamples: /remind 30m Review PR, /remind 2h Deploy, /remind tomorrow Check metrics"
|
|
639
|
-
};
|
|
768
|
+
if (action === "clear") {
|
|
769
|
+
if (args.length < 1) {
|
|
770
|
+
return { handled: true, output: pc.yellow("Usage: /memory clear <category>") };
|
|
640
771
|
}
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
return { handled: true, remind: { timeStr, message } };
|
|
772
|
+
const output = await mcpWrite(ctx, "memory", "memory_forget", { category: args[0] });
|
|
773
|
+
return { handled: true, output };
|
|
644
774
|
}
|
|
645
|
-
|
|
775
|
+
return { handled: true, output: pc.yellow(`Unknown action: /memory ${action}. Use /memory [search|clear].`) };
|
|
776
|
+
}
|
|
777
|
+
function handleStatusCommand(ctx) {
|
|
778
|
+
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
779
|
+
const amemConnected = mcpToolCount > 0;
|
|
780
|
+
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
781
|
+
const lines = [pc.bold("Aman Ecosystem Dashboard"), ""];
|
|
782
|
+
for (const layer of status.layers) {
|
|
783
|
+
const icon = layer.exists ? pc.green("\u25CF") : pc.dim("\u25CB");
|
|
784
|
+
const name = pc.bold(layer.name.padEnd(12));
|
|
785
|
+
const summary = layer.exists ? layer.summary : pc.dim("not configured");
|
|
786
|
+
lines.push(` ${icon} ${name} ${summary}`);
|
|
787
|
+
}
|
|
788
|
+
lines.push("");
|
|
789
|
+
lines.push(` ${status.mcpConnected ? pc.green("\u25CF") : pc.dim("\u25CB")} ${pc.bold("MCP".padEnd(12))} ${status.mcpConnected ? `${status.mcpToolCount} tools available` : pc.dim("not connected")}`);
|
|
790
|
+
lines.push(` ${status.amemConnected ? pc.green("\u25CF") : pc.dim("\u25CB")} ${pc.bold("Memory".padEnd(12))} ${status.amemConnected ? "connected" : pc.dim("not connected")}`);
|
|
791
|
+
return { handled: true, output: lines.join("\n") };
|
|
792
|
+
}
|
|
793
|
+
function handleDoctorCommand(ctx) {
|
|
794
|
+
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
795
|
+
const amemConnected = mcpToolCount > 0;
|
|
796
|
+
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
797
|
+
const lines = [pc.bold("Aman Health Check"), ""];
|
|
798
|
+
for (const layer of status.layers) {
|
|
799
|
+
const check = layer.exists ? pc.green("\u2713") : pc.red("\u2717");
|
|
800
|
+
const name = layer.name.padEnd(12);
|
|
801
|
+
const detail = layer.exists ? pc.green(layer.summary) : pc.red("missing");
|
|
802
|
+
lines.push(` ${check} ${name} ${detail}`);
|
|
803
|
+
}
|
|
804
|
+
lines.push("");
|
|
805
|
+
lines.push(` ${status.mcpConnected ? pc.green("\u2713") : pc.red("\u2717")} ${"MCP".padEnd(12)} ${status.mcpConnected ? pc.green(`${status.mcpToolCount} tools`) : pc.red("not connected")}`);
|
|
806
|
+
lines.push(` ${status.amemConnected ? pc.green("\u2713") : pc.red("\u2717")} ${"Memory".padEnd(12)} ${status.amemConnected ? pc.green("connected") : pc.red("not connected")}`);
|
|
807
|
+
return { handled: true, output: lines.join("\n") };
|
|
808
|
+
}
|
|
809
|
+
function handleHelp() {
|
|
810
|
+
return {
|
|
811
|
+
handled: true,
|
|
812
|
+
output: [
|
|
813
|
+
pc.bold("Commands:"),
|
|
814
|
+
` ${pc.cyan("/help")} Show this help`,
|
|
815
|
+
` ${pc.cyan("/identity")} View identity [update <section>]`,
|
|
816
|
+
` ${pc.cyan("/rules")} View rules [add|remove|toggle ...]`,
|
|
817
|
+
` ${pc.cyan("/workflows")} View workflows [add|remove ...]`,
|
|
818
|
+
` ${pc.cyan("/tools")} View tools [add|remove ...]`,
|
|
819
|
+
` ${pc.cyan("/skills")} View skills [install|uninstall ...]`,
|
|
820
|
+
` ${pc.cyan("/eval")} View evaluation [milestone ...]`,
|
|
821
|
+
` ${pc.cyan("/memory")} View recent memories [search|clear ...]`,
|
|
822
|
+
` ${pc.cyan("/status")} Ecosystem dashboard`,
|
|
823
|
+
` ${pc.cyan("/doctor")} Health check all layers`,
|
|
824
|
+
` ${pc.cyan("/remind")} Set a reminder`,
|
|
825
|
+
` ${pc.cyan("/model")} Show current LLM model`,
|
|
826
|
+
` ${pc.cyan("/update")} Check for updates`,
|
|
827
|
+
` ${pc.cyan("/reconfig")} Reset LLM config`,
|
|
828
|
+
` ${pc.cyan("/clear")} Clear conversation history`,
|
|
829
|
+
` ${pc.cyan("/quit")} Exit`
|
|
830
|
+
].join("\n")
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function handleRemind(input) {
|
|
834
|
+
const parts = input.trim().split(/\s+/);
|
|
835
|
+
if (parts.length < 3) {
|
|
646
836
|
return {
|
|
647
837
|
handled: true,
|
|
648
|
-
output:
|
|
838
|
+
output: "Usage: /remind <time> <message>\nExamples: /remind 30m Review PR, /remind 2h Deploy, /remind tomorrow Check metrics"
|
|
649
839
|
};
|
|
650
840
|
}
|
|
651
|
-
|
|
841
|
+
const timeStr = parts[1];
|
|
842
|
+
const message = parts.slice(2).join(" ");
|
|
843
|
+
return { handled: true, remind: { timeStr, message } };
|
|
844
|
+
}
|
|
845
|
+
function handleReconfig() {
|
|
846
|
+
const configPath = path4.join(os4.homedir(), ".aman-agent", "config.json");
|
|
847
|
+
if (fs4.existsSync(configPath)) {
|
|
848
|
+
fs4.unlinkSync(configPath);
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
handled: true,
|
|
852
|
+
quit: true,
|
|
853
|
+
output: [
|
|
854
|
+
pc.green("Config reset."),
|
|
855
|
+
`Run ${pc.bold("npx @aman_asmuei/aman-agent")} again to reconfigure your LLM provider, model, and API key.`
|
|
856
|
+
].join("\n")
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function handleUpdate() {
|
|
860
|
+
try {
|
|
861
|
+
const current = execFileSync("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
862
|
+
const local = JSON.parse(fs4.readFileSync(path4.join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
863
|
+
if (current === local) {
|
|
864
|
+
return { handled: true, output: `${pc.green("Up to date")} \u2014 v${local}` };
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
handled: true,
|
|
868
|
+
output: [
|
|
869
|
+
`${pc.yellow("Update available:")} v${local} \u2192 v${current}`,
|
|
870
|
+
"",
|
|
871
|
+
`Run this in your terminal:`,
|
|
872
|
+
` ${pc.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
873
|
+
"",
|
|
874
|
+
`Or use npx (always latest):`,
|
|
875
|
+
` ${pc.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
876
|
+
].join("\n")
|
|
877
|
+
};
|
|
878
|
+
} catch {
|
|
879
|
+
return {
|
|
880
|
+
handled: true,
|
|
881
|
+
output: [
|
|
882
|
+
`To update, run in your terminal:`,
|
|
883
|
+
` ${pc.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
884
|
+
"",
|
|
885
|
+
`Or use npx (always latest):`,
|
|
886
|
+
` ${pc.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
887
|
+
].join("\n")
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async function handleCommand(input, ctx) {
|
|
892
|
+
const trimmed = input.trim();
|
|
893
|
+
if (!trimmed.startsWith("/")) return { handled: false };
|
|
894
|
+
const { base, action, args } = parseCommand(trimmed);
|
|
895
|
+
switch (base) {
|
|
896
|
+
case "quit":
|
|
897
|
+
case "exit":
|
|
898
|
+
case "q":
|
|
899
|
+
return { handled: true, quit: true };
|
|
900
|
+
case "help":
|
|
901
|
+
return handleHelp();
|
|
902
|
+
case "clear":
|
|
903
|
+
return { handled: true, output: pc.dim("Conversation cleared."), clearHistory: true };
|
|
904
|
+
case "model":
|
|
905
|
+
return { handled: true, output: ctx.model ? `Model: ${pc.bold(ctx.model)}` : "Model: unknown" };
|
|
906
|
+
case "identity":
|
|
907
|
+
return handleIdentityCommand(action, args, ctx);
|
|
908
|
+
case "rules":
|
|
909
|
+
return handleRulesCommand(action, args, ctx);
|
|
910
|
+
case "workflows":
|
|
911
|
+
return handleWorkflowsCommand(action, args, ctx);
|
|
912
|
+
case "tools":
|
|
913
|
+
return handleToolsCommand(action, args, ctx);
|
|
914
|
+
case "skills":
|
|
915
|
+
return handleSkillsCommand(action, args, ctx);
|
|
916
|
+
case "eval":
|
|
917
|
+
return handleEvalCommand(action, args, ctx);
|
|
918
|
+
case "memory":
|
|
919
|
+
return handleMemoryCommand(action, args, ctx);
|
|
920
|
+
case "status":
|
|
921
|
+
return handleStatusCommand(ctx);
|
|
922
|
+
case "doctor":
|
|
923
|
+
return handleDoctorCommand(ctx);
|
|
924
|
+
case "remind":
|
|
925
|
+
return handleRemind(trimmed);
|
|
926
|
+
case "update-config":
|
|
927
|
+
case "reconfig":
|
|
928
|
+
return handleReconfig();
|
|
929
|
+
case "update":
|
|
930
|
+
case "upgrade":
|
|
931
|
+
return handleUpdate();
|
|
932
|
+
default:
|
|
933
|
+
return { handled: true, output: `Unknown command: /${base}. Type ${pc.cyan("/help")} for available commands.` };
|
|
934
|
+
}
|
|
652
935
|
}
|
|
653
936
|
|
|
654
937
|
// src/reminders.ts
|
|
@@ -690,14 +973,175 @@ function clearReminders() {
|
|
|
690
973
|
activeReminders.length = 0;
|
|
691
974
|
}
|
|
692
975
|
|
|
976
|
+
// src/hooks.ts
|
|
977
|
+
import * as p from "@clack/prompts";
|
|
978
|
+
var isHookCall = false;
|
|
979
|
+
async function onSessionStart(ctx) {
|
|
980
|
+
let greeting = "";
|
|
981
|
+
let contextInjection = "";
|
|
982
|
+
if (ctx.config.memoryRecall) {
|
|
983
|
+
try {
|
|
984
|
+
isHookCall = true;
|
|
985
|
+
const result = await ctx.mcpManager.callTool("memory_context", {});
|
|
986
|
+
if (result && !result.startsWith("Error")) {
|
|
987
|
+
greeting += result;
|
|
988
|
+
}
|
|
989
|
+
} catch {
|
|
990
|
+
} finally {
|
|
991
|
+
isHookCall = false;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (ctx.config.sessionResume) {
|
|
995
|
+
try {
|
|
996
|
+
isHookCall = true;
|
|
997
|
+
const result = await ctx.mcpManager.callTool("identity_summary", {});
|
|
998
|
+
if (result && !result.startsWith("Error")) {
|
|
999
|
+
if (greeting) greeting += "\n";
|
|
1000
|
+
greeting += result;
|
|
1001
|
+
}
|
|
1002
|
+
} catch {
|
|
1003
|
+
} finally {
|
|
1004
|
+
isHookCall = false;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (greeting) {
|
|
1008
|
+
contextInjection = `<session-context>
|
|
1009
|
+
${greeting}
|
|
1010
|
+
</session-context>`;
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
greeting: greeting || void 0,
|
|
1014
|
+
contextInjection: contextInjection || void 0
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
async function onBeforeToolExec(toolName, toolArgs, ctx) {
|
|
1018
|
+
if (!ctx.config.rulesCheck || isHookCall) {
|
|
1019
|
+
return { allow: true };
|
|
1020
|
+
}
|
|
1021
|
+
if (toolName === "rules_check") {
|
|
1022
|
+
return { allow: true };
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
isHookCall = true;
|
|
1026
|
+
const description = `${toolName}(${JSON.stringify(toolArgs)})`;
|
|
1027
|
+
const result = await ctx.mcpManager.callTool("rules_check", {
|
|
1028
|
+
action: description
|
|
1029
|
+
});
|
|
1030
|
+
try {
|
|
1031
|
+
const parsed = JSON.parse(result);
|
|
1032
|
+
if (parsed.violations && parsed.violations.length > 0) {
|
|
1033
|
+
return {
|
|
1034
|
+
allow: false,
|
|
1035
|
+
reason: parsed.violations.join("; ")
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
} catch {
|
|
1039
|
+
}
|
|
1040
|
+
return { allow: true };
|
|
1041
|
+
} catch {
|
|
1042
|
+
return { allow: true };
|
|
1043
|
+
} finally {
|
|
1044
|
+
isHookCall = false;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async function onWorkflowMatch(userInput, ctx) {
|
|
1048
|
+
if (!ctx.config.workflowSuggest) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
try {
|
|
1052
|
+
isHookCall = true;
|
|
1053
|
+
const result = await ctx.mcpManager.callTool("workflow_list", {});
|
|
1054
|
+
const workflows = JSON.parse(result);
|
|
1055
|
+
const inputLower = userInput.toLowerCase();
|
|
1056
|
+
for (const wf of workflows) {
|
|
1057
|
+
const nameLower = wf.name.toLowerCase();
|
|
1058
|
+
if (inputLower.includes(nameLower)) {
|
|
1059
|
+
const steps = (wf.steps || []).map((s, i) => `${i + 1}. ${s}`).join("\n");
|
|
1060
|
+
return { name: wf.name, steps };
|
|
1061
|
+
}
|
|
1062
|
+
if (wf.description) {
|
|
1063
|
+
const words = wf.description.split(/\s+/).filter((w) => w.length > 4).map((w) => w.toLowerCase());
|
|
1064
|
+
for (const word of words) {
|
|
1065
|
+
if (inputLower.includes(word)) {
|
|
1066
|
+
const steps = (wf.steps || []).map((s, i) => `${i + 1}. ${s}`).join("\n");
|
|
1067
|
+
return { name: wf.name, steps };
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return null;
|
|
1073
|
+
} catch {
|
|
1074
|
+
return null;
|
|
1075
|
+
} finally {
|
|
1076
|
+
isHookCall = false;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async function onSessionEnd(ctx, messages) {
|
|
1080
|
+
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") {
|
|
1093
|
+
try {
|
|
1094
|
+
isHookCall = true;
|
|
1095
|
+
await ctx.mcpManager.callTool("eval_log", {
|
|
1096
|
+
rating,
|
|
1097
|
+
highlights: "Quick session rating",
|
|
1098
|
+
improvements: ""
|
|
1099
|
+
});
|
|
1100
|
+
} finally {
|
|
1101
|
+
isHookCall = false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
1106
|
+
let lastUserMsg = "";
|
|
1107
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1108
|
+
if (messages[i].role === "user" && typeof messages[i].content === "string") {
|
|
1109
|
+
lastUserMsg = messages[i].content;
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (lastUserMsg) {
|
|
1114
|
+
try {
|
|
1115
|
+
isHookCall = true;
|
|
1116
|
+
await ctx.mcpManager.callTool("identity_update_session", {
|
|
1117
|
+
resume: lastUserMsg.slice(0, 200),
|
|
1118
|
+
topics: "See conversation history",
|
|
1119
|
+
decisions: "See conversation history"
|
|
1120
|
+
});
|
|
1121
|
+
} finally {
|
|
1122
|
+
isHookCall = false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
693
1130
|
// src/agent.ts
|
|
694
|
-
async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager) {
|
|
1131
|
+
async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager, hooksConfig) {
|
|
695
1132
|
const messages = [];
|
|
696
1133
|
const rl = readline.createInterface({
|
|
697
1134
|
input: process.stdin,
|
|
698
1135
|
output: process.stdout
|
|
699
1136
|
});
|
|
700
|
-
rl.on("SIGINT", () => {
|
|
1137
|
+
rl.on("SIGINT", async () => {
|
|
1138
|
+
if (mcpManager && hooksConfig) {
|
|
1139
|
+
try {
|
|
1140
|
+
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1141
|
+
await onSessionEnd(hookCtx, messages);
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
701
1145
|
console.log(pc3.dim("\nGoodbye.\n"));
|
|
702
1146
|
rl.close();
|
|
703
1147
|
process.exit(0);
|
|
@@ -714,13 +1158,32 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager)
|
|
|
714
1158
|
Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit.
|
|
715
1159
|
`
|
|
716
1160
|
);
|
|
1161
|
+
if (mcpManager && hooksConfig) {
|
|
1162
|
+
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1163
|
+
try {
|
|
1164
|
+
const session = await onSessionStart(hookCtx);
|
|
1165
|
+
if (session.greeting) console.log(pc3.dim(session.greeting));
|
|
1166
|
+
if (session.contextInjection) {
|
|
1167
|
+
messages.push({ role: "user", content: session.contextInjection });
|
|
1168
|
+
messages.push({ role: "assistant", content: "I have context from our previous sessions. How can I help?" });
|
|
1169
|
+
}
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
717
1173
|
while (true) {
|
|
718
1174
|
const input = await prompt();
|
|
719
1175
|
if (!input.trim()) continue;
|
|
720
|
-
const cmdResult = handleCommand(input, model);
|
|
1176
|
+
const cmdResult = await handleCommand(input, { model, mcpManager });
|
|
721
1177
|
if (cmdResult.handled) {
|
|
722
1178
|
if (cmdResult.quit) {
|
|
723
1179
|
clearReminders();
|
|
1180
|
+
if (mcpManager && hooksConfig) {
|
|
1181
|
+
try {
|
|
1182
|
+
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1183
|
+
await onSessionEnd(hookCtx, messages);
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
724
1187
|
console.log(pc3.dim("\nGoodbye.\n"));
|
|
725
1188
|
rl.close();
|
|
726
1189
|
return;
|
|
@@ -747,12 +1210,33 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
747
1210
|
}
|
|
748
1211
|
continue;
|
|
749
1212
|
}
|
|
1213
|
+
let activeSystemPrompt = systemPrompt;
|
|
1214
|
+
if (mcpManager && hooksConfig) {
|
|
1215
|
+
try {
|
|
1216
|
+
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1217
|
+
const wfMatch = await onWorkflowMatch(input, hookCtx);
|
|
1218
|
+
if (wfMatch) {
|
|
1219
|
+
const useIt = await new Promise((resolve) => {
|
|
1220
|
+
rl.question(pc3.dim(` Workflow "${wfMatch.name}" matches. Use it? (y/N) `), (answer) => resolve(answer.toLowerCase() === "y"));
|
|
1221
|
+
});
|
|
1222
|
+
if (useIt) {
|
|
1223
|
+
activeSystemPrompt = systemPrompt + `
|
|
1224
|
+
|
|
1225
|
+
<active-workflow>
|
|
1226
|
+
${wfMatch.steps}
|
|
1227
|
+
</active-workflow>`;
|
|
1228
|
+
console.log(pc3.dim(` Using "${wfMatch.name}" workflow.`));
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
} catch {
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
750
1234
|
messages.push({ role: "user", content: input });
|
|
751
1235
|
process.stdout.write(pc3.cyan(`
|
|
752
1236
|
${aiName} > `));
|
|
753
1237
|
try {
|
|
754
1238
|
let response = await client.chat(
|
|
755
|
-
|
|
1239
|
+
activeSystemPrompt,
|
|
756
1240
|
messages,
|
|
757
1241
|
(chunk) => {
|
|
758
1242
|
if (chunk.type === "text" && chunk.text) {
|
|
@@ -768,6 +1252,21 @@ ${aiName} > `));
|
|
|
768
1252
|
while (response.toolUses.length > 0 && mcpManager) {
|
|
769
1253
|
const toolResults = [];
|
|
770
1254
|
for (const toolUse of response.toolUses) {
|
|
1255
|
+
if (hooksConfig) {
|
|
1256
|
+
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1257
|
+
const check = await onBeforeToolExec(toolUse.name, toolUse.input, hookCtx);
|
|
1258
|
+
if (!check.allow) {
|
|
1259
|
+
process.stdout.write(pc3.red(` [BLOCKED: ${check.reason}]
|
|
1260
|
+
`));
|
|
1261
|
+
toolResults.push({
|
|
1262
|
+
type: "tool_result",
|
|
1263
|
+
tool_use_id: toolUse.id,
|
|
1264
|
+
content: `BLOCKED by guardrail: ${check.reason}`,
|
|
1265
|
+
is_error: true
|
|
1266
|
+
});
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
771
1270
|
process.stdout.write(
|
|
772
1271
|
pc3.dim(` [using ${toolUse.name}...]
|
|
773
1272
|
`)
|
|
@@ -787,7 +1286,7 @@ ${aiName} > `));
|
|
|
787
1286
|
content: toolResults
|
|
788
1287
|
});
|
|
789
1288
|
response = await client.chat(
|
|
790
|
-
|
|
1289
|
+
activeSystemPrompt,
|
|
791
1290
|
messages,
|
|
792
1291
|
(chunk) => {
|
|
793
1292
|
if (chunk.type === "text" && chunk.text) {
|
|
@@ -811,22 +1310,22 @@ Error: ${message}`));
|
|
|
811
1310
|
}
|
|
812
1311
|
|
|
813
1312
|
// src/scheduler.ts
|
|
814
|
-
import
|
|
815
|
-
import
|
|
816
|
-
import
|
|
817
|
-
var SCHEDULES_PATH =
|
|
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");
|
|
818
1317
|
function loadSchedules() {
|
|
819
|
-
if (!
|
|
1318
|
+
if (!fs5.existsSync(SCHEDULES_PATH)) return [];
|
|
820
1319
|
try {
|
|
821
|
-
return JSON.parse(
|
|
1320
|
+
return JSON.parse(fs5.readFileSync(SCHEDULES_PATH, "utf-8"));
|
|
822
1321
|
} catch {
|
|
823
1322
|
return [];
|
|
824
1323
|
}
|
|
825
1324
|
}
|
|
826
1325
|
function saveSchedules(tasks) {
|
|
827
|
-
const dir =
|
|
828
|
-
|
|
829
|
-
|
|
1326
|
+
const dir = path5.dirname(SCHEDULES_PATH);
|
|
1327
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1328
|
+
fs5.writeFileSync(
|
|
830
1329
|
SCHEDULES_PATH,
|
|
831
1330
|
JSON.stringify(tasks, null, 2) + "\n",
|
|
832
1331
|
"utf-8"
|
|
@@ -879,9 +1378,9 @@ function isDue(schedule, lastRun, now) {
|
|
|
879
1378
|
}
|
|
880
1379
|
|
|
881
1380
|
// src/notifications.ts
|
|
882
|
-
import
|
|
883
|
-
import
|
|
884
|
-
import
|
|
1381
|
+
import fs6 from "fs";
|
|
1382
|
+
import path6 from "path";
|
|
1383
|
+
import os6 from "os";
|
|
885
1384
|
import pc4 from "picocolors";
|
|
886
1385
|
function checkNotifications() {
|
|
887
1386
|
const notifications = [];
|
|
@@ -892,9 +1391,9 @@ function checkNotifications() {
|
|
|
892
1391
|
message: `${task.name} (${task.schedule})`
|
|
893
1392
|
});
|
|
894
1393
|
}
|
|
895
|
-
const evalPath =
|
|
896
|
-
if (
|
|
897
|
-
const content =
|
|
1394
|
+
const evalPath = path6.join(os6.homedir(), ".aeval", "eval.md");
|
|
1395
|
+
if (fs6.existsSync(evalPath)) {
|
|
1396
|
+
const content = fs6.readFileSync(evalPath, "utf-8");
|
|
898
1397
|
const dateMatch = content.match(/- Last updated: (.+)$/m);
|
|
899
1398
|
if (dateMatch) {
|
|
900
1399
|
const lastDate = new Date(dateMatch[1]);
|
|
@@ -924,16 +1423,16 @@ function displayNotifications(notifications) {
|
|
|
924
1423
|
}
|
|
925
1424
|
|
|
926
1425
|
// src/index.ts
|
|
927
|
-
import
|
|
928
|
-
import
|
|
929
|
-
import
|
|
1426
|
+
import fs7 from "fs";
|
|
1427
|
+
import path7 from "path";
|
|
1428
|
+
import os7 from "os";
|
|
930
1429
|
var program = new Command();
|
|
931
1430
|
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) => {
|
|
932
|
-
|
|
1431
|
+
p2.intro(pc5.bold("aman agent") + pc5.dim(" \u2014 starting your AI companion"));
|
|
933
1432
|
let config = loadConfig();
|
|
934
1433
|
if (!config) {
|
|
935
|
-
|
|
936
|
-
const provider = await
|
|
1434
|
+
p2.log.info("First-time setup \u2014 configure your LLM connection.");
|
|
1435
|
+
const provider = await p2.select({
|
|
937
1436
|
message: "LLM provider",
|
|
938
1437
|
options: [
|
|
939
1438
|
{
|
|
@@ -946,27 +1445,27 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
946
1445
|
],
|
|
947
1446
|
initialValue: "anthropic"
|
|
948
1447
|
});
|
|
949
|
-
if (
|
|
1448
|
+
if (p2.isCancel(provider)) process.exit(0);
|
|
950
1449
|
let apiKey = "";
|
|
951
1450
|
let defaultModel = "";
|
|
952
1451
|
if (provider === "ollama") {
|
|
953
1452
|
apiKey = "ollama";
|
|
954
|
-
const modelInput = await
|
|
1453
|
+
const modelInput = await p2.text({
|
|
955
1454
|
message: "Ollama model name",
|
|
956
1455
|
placeholder: "llama3.2",
|
|
957
1456
|
defaultValue: "llama3.2"
|
|
958
1457
|
});
|
|
959
|
-
if (
|
|
1458
|
+
if (p2.isCancel(modelInput)) process.exit(0);
|
|
960
1459
|
defaultModel = modelInput || "llama3.2";
|
|
961
1460
|
} else if (provider === "anthropic") {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
apiKey = await
|
|
1461
|
+
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."));
|
|
1463
|
+
apiKey = await p2.text({
|
|
965
1464
|
message: "API key (starts with sk-ant-)",
|
|
966
1465
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
967
1466
|
});
|
|
968
|
-
if (
|
|
969
|
-
const modelChoice = await
|
|
1467
|
+
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1468
|
+
const modelChoice = await p2.select({
|
|
970
1469
|
message: "Claude model",
|
|
971
1470
|
options: [
|
|
972
1471
|
{ value: "claude-sonnet-4-5-20250514", label: "Claude Sonnet 4.5", hint: "fast, recommended" },
|
|
@@ -976,25 +1475,25 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
976
1475
|
],
|
|
977
1476
|
initialValue: "claude-sonnet-4-5-20250514"
|
|
978
1477
|
});
|
|
979
|
-
if (
|
|
1478
|
+
if (p2.isCancel(modelChoice)) process.exit(0);
|
|
980
1479
|
if (modelChoice === "custom") {
|
|
981
|
-
const customModel = await
|
|
1480
|
+
const customModel = await p2.text({
|
|
982
1481
|
message: "Model ID",
|
|
983
1482
|
placeholder: "claude-sonnet-4-5-20250514",
|
|
984
1483
|
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
985
1484
|
});
|
|
986
|
-
if (
|
|
1485
|
+
if (p2.isCancel(customModel)) process.exit(0);
|
|
987
1486
|
defaultModel = customModel;
|
|
988
1487
|
} else {
|
|
989
1488
|
defaultModel = modelChoice;
|
|
990
1489
|
}
|
|
991
1490
|
} else {
|
|
992
|
-
apiKey = await
|
|
1491
|
+
apiKey = await p2.text({
|
|
993
1492
|
message: "API key",
|
|
994
1493
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
995
1494
|
});
|
|
996
|
-
if (
|
|
997
|
-
const modelChoice = await
|
|
1495
|
+
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1496
|
+
const modelChoice = await p2.select({
|
|
998
1497
|
message: "OpenAI model",
|
|
999
1498
|
options: [
|
|
1000
1499
|
{ value: "gpt-4o", label: "GPT-4o", hint: "recommended" },
|
|
@@ -1004,14 +1503,14 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1004
1503
|
],
|
|
1005
1504
|
initialValue: "gpt-4o"
|
|
1006
1505
|
});
|
|
1007
|
-
if (
|
|
1506
|
+
if (p2.isCancel(modelChoice)) process.exit(0);
|
|
1008
1507
|
if (modelChoice === "custom") {
|
|
1009
|
-
const customModel = await
|
|
1508
|
+
const customModel = await p2.text({
|
|
1010
1509
|
message: "Model ID",
|
|
1011
1510
|
placeholder: "gpt-4o",
|
|
1012
1511
|
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
1013
1512
|
});
|
|
1014
|
-
if (
|
|
1513
|
+
if (p2.isCancel(customModel)) process.exit(0);
|
|
1015
1514
|
defaultModel = customModel;
|
|
1016
1515
|
} else {
|
|
1017
1516
|
defaultModel = modelChoice;
|
|
@@ -1019,44 +1518,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1019
1518
|
}
|
|
1020
1519
|
config = { provider, apiKey, model: defaultModel };
|
|
1021
1520
|
saveConfig(config);
|
|
1022
|
-
|
|
1521
|
+
p2.log.success("Config saved to ~/.aman-agent/config.json");
|
|
1023
1522
|
}
|
|
1024
1523
|
const model = options.model || config.model;
|
|
1025
1524
|
const budget = options.budget || void 0;
|
|
1026
1525
|
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
1027
1526
|
if (layers.length === 0) {
|
|
1028
|
-
|
|
1527
|
+
p2.log.warning(
|
|
1029
1528
|
"No ecosystem configured. Run " + pc5.bold("npx @aman_asmuei/aman") + " first."
|
|
1030
1529
|
);
|
|
1031
|
-
|
|
1530
|
+
p2.log.info("Starting with empty system prompt.");
|
|
1032
1531
|
} else {
|
|
1033
|
-
|
|
1532
|
+
p2.log.success(
|
|
1034
1533
|
`Loaded: ${layers.join(", ")} ${pc5.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
|
|
1035
1534
|
);
|
|
1036
1535
|
if (truncated.length > 0) {
|
|
1037
|
-
|
|
1536
|
+
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc5.dim("(over budget)")}`);
|
|
1038
1537
|
}
|
|
1039
1538
|
}
|
|
1040
|
-
|
|
1041
|
-
const corePath =
|
|
1539
|
+
p2.log.info(`Model: ${pc5.dim(model)}`);
|
|
1540
|
+
const corePath = path7.join(os7.homedir(), ".acore", "core.md");
|
|
1042
1541
|
let aiName = "Assistant";
|
|
1043
|
-
if (
|
|
1044
|
-
const content =
|
|
1542
|
+
if (fs7.existsSync(corePath)) {
|
|
1543
|
+
const content = fs7.readFileSync(corePath, "utf-8");
|
|
1045
1544
|
const match = content.match(/^# (.+)$/m);
|
|
1046
1545
|
if (match) aiName = match[1];
|
|
1047
1546
|
}
|
|
1048
|
-
|
|
1547
|
+
p2.log.success(`${pc5.bold(aiName)} is ready.`);
|
|
1049
1548
|
const notifications = checkNotifications();
|
|
1050
1549
|
displayNotifications(notifications);
|
|
1051
1550
|
const mcpManager = new McpManager();
|
|
1052
|
-
|
|
1551
|
+
p2.log.step("Connecting to MCP servers...");
|
|
1053
1552
|
await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
|
|
1054
1553
|
await mcpManager.connect("amem", "npx", ["-y", "@aman_asmuei/amem"]);
|
|
1055
1554
|
const mcpTools = mcpManager.getTools();
|
|
1056
1555
|
if (mcpTools.length > 0) {
|
|
1057
|
-
|
|
1556
|
+
p2.log.success(`${mcpTools.length} MCP tools available`);
|
|
1058
1557
|
} else {
|
|
1059
|
-
|
|
1558
|
+
p2.log.info(
|
|
1060
1559
|
"No MCP tools connected (install aman-mcp or amem for tool support)"
|
|
1061
1560
|
);
|
|
1062
1561
|
}
|
|
@@ -1079,7 +1578,8 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1079
1578
|
aiName,
|
|
1080
1579
|
model,
|
|
1081
1580
|
toolDefs.length > 0 ? toolDefs : void 0,
|
|
1082
|
-
toolDefs.length > 0 ? mcpManager : void 0
|
|
1581
|
+
toolDefs.length > 0 ? mcpManager : void 0,
|
|
1582
|
+
config.hooks
|
|
1083
1583
|
);
|
|
1084
1584
|
await mcpManager.disconnect();
|
|
1085
1585
|
});
|
|
@@ -1100,12 +1600,12 @@ program.command("schedule").description("Manage scheduled tasks").argument("[act
|
|
|
1100
1600
|
return;
|
|
1101
1601
|
}
|
|
1102
1602
|
if (action === "add") {
|
|
1103
|
-
const name = await
|
|
1603
|
+
const name = await p2.text({
|
|
1104
1604
|
message: "Task name?",
|
|
1105
1605
|
validate: (v) => v.length === 0 ? "Name is required" : void 0
|
|
1106
1606
|
});
|
|
1107
|
-
if (
|
|
1108
|
-
const schedule = await
|
|
1607
|
+
if (p2.isCancel(name)) return;
|
|
1608
|
+
const schedule = await p2.select({
|
|
1109
1609
|
message: "Schedule?",
|
|
1110
1610
|
options: [
|
|
1111
1611
|
{ value: "daily 9am", label: "Daily at 9am" },
|
|
@@ -1115,23 +1615,23 @@ program.command("schedule").description("Manage scheduled tasks").argument("[act
|
|
|
1115
1615
|
{ value: "every 4h", label: "Every 4 hours" }
|
|
1116
1616
|
]
|
|
1117
1617
|
});
|
|
1118
|
-
if (
|
|
1119
|
-
const actionType = await
|
|
1618
|
+
if (p2.isCancel(schedule)) return;
|
|
1619
|
+
const actionType = await p2.select({
|
|
1120
1620
|
message: "What should happen?",
|
|
1121
1621
|
options: [
|
|
1122
1622
|
{ value: "notify", label: "Show notification" },
|
|
1123
1623
|
{ value: "auto-run", label: "Run automatically" }
|
|
1124
1624
|
]
|
|
1125
1625
|
});
|
|
1126
|
-
if (
|
|
1626
|
+
if (p2.isCancel(actionType)) return;
|
|
1127
1627
|
let taskAction = "notify";
|
|
1128
1628
|
if (actionType === "auto-run") {
|
|
1129
|
-
const cmd = await
|
|
1629
|
+
const cmd = await p2.text({
|
|
1130
1630
|
message: "Command to run?",
|
|
1131
1631
|
placeholder: "e.g. run:daily-standup",
|
|
1132
1632
|
validate: (v) => v.length === 0 ? "Command is required" : void 0
|
|
1133
1633
|
});
|
|
1134
|
-
if (
|
|
1634
|
+
if (p2.isCancel(cmd)) return;
|
|
1135
1635
|
taskAction = cmd;
|
|
1136
1636
|
}
|
|
1137
1637
|
const task = addSchedule({
|