@aman_asmuei/aman-agent 0.2.3 → 0.3.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 +682 -195
- 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
|
-
|
|
772
|
+
const output = await mcpWrite(ctx, "memory", "memory_forget", { category: args[0] });
|
|
773
|
+
return { handled: true, output };
|
|
774
|
+
}
|
|
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) {
|
|
836
|
+
return {
|
|
837
|
+
handled: true,
|
|
838
|
+
output: "Usage: /remind <time> <message>\nExamples: /remind 30m Review PR, /remind 2h Deploy, /remind tomorrow Check metrics"
|
|
839
|
+
};
|
|
644
840
|
}
|
|
645
|
-
|
|
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 {
|
|
646
879
|
return {
|
|
647
880
|
handled: true,
|
|
648
|
-
output:
|
|
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")
|
|
649
888
|
};
|
|
650
889
|
}
|
|
651
|
-
|
|
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,40 +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
|
-
|
|
965
|
-
|
|
966
|
-
{ value: "subscription", label: "Claude Pro/Team subscription", hint: "uses claude.ai session" }
|
|
967
|
-
],
|
|
968
|
-
initialValue: "api-key"
|
|
969
|
-
});
|
|
970
|
-
if (p.isCancel(authMethod)) process.exit(0);
|
|
971
|
-
if (authMethod === "subscription") {
|
|
972
|
-
p.log.info("To use your Claude subscription:");
|
|
973
|
-
p.log.info("1. Go to https://console.anthropic.com/settings/keys");
|
|
974
|
-
p.log.info("2. Create an API key (included with Pro/Team plan)");
|
|
975
|
-
p.log.info("3. Paste it below");
|
|
976
|
-
}
|
|
977
|
-
apiKey = await p.text({
|
|
978
|
-
message: "API key",
|
|
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({
|
|
1464
|
+
message: "API key (starts with sk-ant-)",
|
|
979
1465
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
980
1466
|
});
|
|
981
|
-
if (
|
|
982
|
-
const modelChoice = await
|
|
1467
|
+
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1468
|
+
const modelChoice = await p2.select({
|
|
983
1469
|
message: "Claude model",
|
|
984
1470
|
options: [
|
|
985
1471
|
{ value: "claude-sonnet-4-5-20250514", label: "Claude Sonnet 4.5", hint: "fast, recommended" },
|
|
@@ -989,25 +1475,25 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
989
1475
|
],
|
|
990
1476
|
initialValue: "claude-sonnet-4-5-20250514"
|
|
991
1477
|
});
|
|
992
|
-
if (
|
|
1478
|
+
if (p2.isCancel(modelChoice)) process.exit(0);
|
|
993
1479
|
if (modelChoice === "custom") {
|
|
994
|
-
const customModel = await
|
|
1480
|
+
const customModel = await p2.text({
|
|
995
1481
|
message: "Model ID",
|
|
996
1482
|
placeholder: "claude-sonnet-4-5-20250514",
|
|
997
1483
|
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
998
1484
|
});
|
|
999
|
-
if (
|
|
1485
|
+
if (p2.isCancel(customModel)) process.exit(0);
|
|
1000
1486
|
defaultModel = customModel;
|
|
1001
1487
|
} else {
|
|
1002
1488
|
defaultModel = modelChoice;
|
|
1003
1489
|
}
|
|
1004
1490
|
} else {
|
|
1005
|
-
apiKey = await
|
|
1491
|
+
apiKey = await p2.text({
|
|
1006
1492
|
message: "API key",
|
|
1007
1493
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
1008
1494
|
});
|
|
1009
|
-
if (
|
|
1010
|
-
const modelChoice = await
|
|
1495
|
+
if (p2.isCancel(apiKey)) process.exit(0);
|
|
1496
|
+
const modelChoice = await p2.select({
|
|
1011
1497
|
message: "OpenAI model",
|
|
1012
1498
|
options: [
|
|
1013
1499
|
{ value: "gpt-4o", label: "GPT-4o", hint: "recommended" },
|
|
@@ -1017,14 +1503,14 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1017
1503
|
],
|
|
1018
1504
|
initialValue: "gpt-4o"
|
|
1019
1505
|
});
|
|
1020
|
-
if (
|
|
1506
|
+
if (p2.isCancel(modelChoice)) process.exit(0);
|
|
1021
1507
|
if (modelChoice === "custom") {
|
|
1022
|
-
const customModel = await
|
|
1508
|
+
const customModel = await p2.text({
|
|
1023
1509
|
message: "Model ID",
|
|
1024
1510
|
placeholder: "gpt-4o",
|
|
1025
1511
|
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
1026
1512
|
});
|
|
1027
|
-
if (
|
|
1513
|
+
if (p2.isCancel(customModel)) process.exit(0);
|
|
1028
1514
|
defaultModel = customModel;
|
|
1029
1515
|
} else {
|
|
1030
1516
|
defaultModel = modelChoice;
|
|
@@ -1032,44 +1518,44 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1032
1518
|
}
|
|
1033
1519
|
config = { provider, apiKey, model: defaultModel };
|
|
1034
1520
|
saveConfig(config);
|
|
1035
|
-
|
|
1521
|
+
p2.log.success("Config saved to ~/.aman-agent/config.json");
|
|
1036
1522
|
}
|
|
1037
1523
|
const model = options.model || config.model;
|
|
1038
1524
|
const budget = options.budget || void 0;
|
|
1039
1525
|
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
1040
1526
|
if (layers.length === 0) {
|
|
1041
|
-
|
|
1527
|
+
p2.log.warning(
|
|
1042
1528
|
"No ecosystem configured. Run " + pc5.bold("npx @aman_asmuei/aman") + " first."
|
|
1043
1529
|
);
|
|
1044
|
-
|
|
1530
|
+
p2.log.info("Starting with empty system prompt.");
|
|
1045
1531
|
} else {
|
|
1046
|
-
|
|
1532
|
+
p2.log.success(
|
|
1047
1533
|
`Loaded: ${layers.join(", ")} ${pc5.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
|
|
1048
1534
|
);
|
|
1049
1535
|
if (truncated.length > 0) {
|
|
1050
|
-
|
|
1536
|
+
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc5.dim("(over budget)")}`);
|
|
1051
1537
|
}
|
|
1052
1538
|
}
|
|
1053
|
-
|
|
1054
|
-
const corePath =
|
|
1539
|
+
p2.log.info(`Model: ${pc5.dim(model)}`);
|
|
1540
|
+
const corePath = path7.join(os7.homedir(), ".acore", "core.md");
|
|
1055
1541
|
let aiName = "Assistant";
|
|
1056
|
-
if (
|
|
1057
|
-
const content =
|
|
1542
|
+
if (fs7.existsSync(corePath)) {
|
|
1543
|
+
const content = fs7.readFileSync(corePath, "utf-8");
|
|
1058
1544
|
const match = content.match(/^# (.+)$/m);
|
|
1059
1545
|
if (match) aiName = match[1];
|
|
1060
1546
|
}
|
|
1061
|
-
|
|
1547
|
+
p2.log.success(`${pc5.bold(aiName)} is ready.`);
|
|
1062
1548
|
const notifications = checkNotifications();
|
|
1063
1549
|
displayNotifications(notifications);
|
|
1064
1550
|
const mcpManager = new McpManager();
|
|
1065
|
-
|
|
1551
|
+
p2.log.step("Connecting to MCP servers...");
|
|
1066
1552
|
await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
|
|
1067
1553
|
await mcpManager.connect("amem", "npx", ["-y", "@aman_asmuei/amem"]);
|
|
1068
1554
|
const mcpTools = mcpManager.getTools();
|
|
1069
1555
|
if (mcpTools.length > 0) {
|
|
1070
|
-
|
|
1556
|
+
p2.log.success(`${mcpTools.length} MCP tools available`);
|
|
1071
1557
|
} else {
|
|
1072
|
-
|
|
1558
|
+
p2.log.info(
|
|
1073
1559
|
"No MCP tools connected (install aman-mcp or amem for tool support)"
|
|
1074
1560
|
);
|
|
1075
1561
|
}
|
|
@@ -1092,7 +1578,8 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1092
1578
|
aiName,
|
|
1093
1579
|
model,
|
|
1094
1580
|
toolDefs.length > 0 ? toolDefs : void 0,
|
|
1095
|
-
toolDefs.length > 0 ? mcpManager : void 0
|
|
1581
|
+
toolDefs.length > 0 ? mcpManager : void 0,
|
|
1582
|
+
config.hooks
|
|
1096
1583
|
);
|
|
1097
1584
|
await mcpManager.disconnect();
|
|
1098
1585
|
});
|
|
@@ -1113,12 +1600,12 @@ program.command("schedule").description("Manage scheduled tasks").argument("[act
|
|
|
1113
1600
|
return;
|
|
1114
1601
|
}
|
|
1115
1602
|
if (action === "add") {
|
|
1116
|
-
const name = await
|
|
1603
|
+
const name = await p2.text({
|
|
1117
1604
|
message: "Task name?",
|
|
1118
1605
|
validate: (v) => v.length === 0 ? "Name is required" : void 0
|
|
1119
1606
|
});
|
|
1120
|
-
if (
|
|
1121
|
-
const schedule = await
|
|
1607
|
+
if (p2.isCancel(name)) return;
|
|
1608
|
+
const schedule = await p2.select({
|
|
1122
1609
|
message: "Schedule?",
|
|
1123
1610
|
options: [
|
|
1124
1611
|
{ value: "daily 9am", label: "Daily at 9am" },
|
|
@@ -1128,23 +1615,23 @@ program.command("schedule").description("Manage scheduled tasks").argument("[act
|
|
|
1128
1615
|
{ value: "every 4h", label: "Every 4 hours" }
|
|
1129
1616
|
]
|
|
1130
1617
|
});
|
|
1131
|
-
if (
|
|
1132
|
-
const actionType = await
|
|
1618
|
+
if (p2.isCancel(schedule)) return;
|
|
1619
|
+
const actionType = await p2.select({
|
|
1133
1620
|
message: "What should happen?",
|
|
1134
1621
|
options: [
|
|
1135
1622
|
{ value: "notify", label: "Show notification" },
|
|
1136
1623
|
{ value: "auto-run", label: "Run automatically" }
|
|
1137
1624
|
]
|
|
1138
1625
|
});
|
|
1139
|
-
if (
|
|
1626
|
+
if (p2.isCancel(actionType)) return;
|
|
1140
1627
|
let taskAction = "notify";
|
|
1141
1628
|
if (actionType === "auto-run") {
|
|
1142
|
-
const cmd = await
|
|
1629
|
+
const cmd = await p2.text({
|
|
1143
1630
|
message: "Command to run?",
|
|
1144
1631
|
placeholder: "e.g. run:daily-standup",
|
|
1145
1632
|
validate: (v) => v.length === 0 ? "Command is required" : void 0
|
|
1146
1633
|
});
|
|
1147
|
-
if (
|
|
1634
|
+
if (p2.isCancel(cmd)) return;
|
|
1148
1635
|
taskAction = cmd;
|
|
1149
1636
|
}
|
|
1150
1637
|
const task = addSchedule({
|