@aman_asmuei/aman-agent 0.18.0 → 0.21.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/README.md +17 -1
- package/dist/index.js +708 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -726,6 +726,474 @@ function createOllamaClient(model, baseURL) {
|
|
|
726
726
|
};
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
+
// src/llm/claude-code.ts
|
|
730
|
+
import { spawn, execFileSync } from "child_process";
|
|
731
|
+
function isClaudeCliInstalled() {
|
|
732
|
+
try {
|
|
733
|
+
execFileSync("which", ["claude"], { stdio: "ignore" });
|
|
734
|
+
return true;
|
|
735
|
+
} catch {
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function extractText(content) {
|
|
740
|
+
if (typeof content === "string") return content;
|
|
741
|
+
return content.map((block) => {
|
|
742
|
+
if (block.type === "text") return block.text;
|
|
743
|
+
if (block.type === "tool_result")
|
|
744
|
+
return `[Tool result for ${block.tool_use_id}]: ${block.content}`;
|
|
745
|
+
if (block.type === "tool_use") return `[Used tool: ${block.name}]`;
|
|
746
|
+
return "";
|
|
747
|
+
}).filter(Boolean).join("\n");
|
|
748
|
+
}
|
|
749
|
+
function formatConversation(systemPrompt, messages, tools) {
|
|
750
|
+
const parts = [];
|
|
751
|
+
let fullSystem = systemPrompt;
|
|
752
|
+
if (tools && tools.length > 0) {
|
|
753
|
+
fullSystem += "\n\n## Available Tools\n";
|
|
754
|
+
fullSystem += "You have access to the following tools. To use a tool, respond with a JSON block in this exact format:\n";
|
|
755
|
+
fullSystem += '```json\n{"tool_use": {"id": "call_1", "name": "tool_name", "input": {\u2026}}}\n```\n\n';
|
|
756
|
+
for (const tool of tools) {
|
|
757
|
+
fullSystem += `### ${tool.name}
|
|
758
|
+
${tool.description}
|
|
759
|
+
Parameters: ${JSON.stringify(tool.input_schema)}
|
|
760
|
+
|
|
761
|
+
`;
|
|
762
|
+
}
|
|
763
|
+
fullSystem += "You may include multiple tool_use blocks. After each tool use, you will receive the result and can continue.\n";
|
|
764
|
+
}
|
|
765
|
+
if (messages.length > 1) {
|
|
766
|
+
parts.push("<conversation_history>");
|
|
767
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
768
|
+
const msg = messages[i];
|
|
769
|
+
const role = msg.role === "user" ? "User" : "Assistant";
|
|
770
|
+
const text3 = extractText(msg.content);
|
|
771
|
+
parts.push(`[${role}]: ${text3}`);
|
|
772
|
+
}
|
|
773
|
+
parts.push("</conversation_history>\n");
|
|
774
|
+
}
|
|
775
|
+
const lastMsg = messages[messages.length - 1];
|
|
776
|
+
if (lastMsg) {
|
|
777
|
+
parts.push(extractText(lastMsg.content));
|
|
778
|
+
}
|
|
779
|
+
return { prompt: parts.join("\n"), systemPrompt: fullSystem };
|
|
780
|
+
}
|
|
781
|
+
function parseToolUses(text3) {
|
|
782
|
+
const toolUses = [];
|
|
783
|
+
const codeBlockRegex = /```json\s*\n?([\s\S]*?)```/g;
|
|
784
|
+
let match;
|
|
785
|
+
while ((match = codeBlockRegex.exec(text3)) !== null) {
|
|
786
|
+
try {
|
|
787
|
+
const parsed = JSON.parse(match[1].trim());
|
|
788
|
+
if (parsed.tool_use) {
|
|
789
|
+
toolUses.push({
|
|
790
|
+
id: parsed.tool_use.id || `call_${toolUses.length + 1}`,
|
|
791
|
+
name: parsed.tool_use.name,
|
|
792
|
+
input: parsed.tool_use.input || {}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (toolUses.length === 0) {
|
|
799
|
+
const inlineRegex = /\{"tool_use"\s*:\s*\{[^}]*"name"\s*:\s*"[^"]+?"[^}]*\}\s*\}/g;
|
|
800
|
+
while ((match = inlineRegex.exec(text3)) !== null) {
|
|
801
|
+
try {
|
|
802
|
+
const parsed = JSON.parse(match[0]);
|
|
803
|
+
if (parsed.tool_use) {
|
|
804
|
+
toolUses.push({
|
|
805
|
+
id: parsed.tool_use.id || `call_${toolUses.length + 1}`,
|
|
806
|
+
name: parsed.tool_use.name,
|
|
807
|
+
input: parsed.tool_use.input || {}
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return toolUses;
|
|
815
|
+
}
|
|
816
|
+
function createClaudeCodeClient(model) {
|
|
817
|
+
return {
|
|
818
|
+
async chat(systemPrompt, messages, onChunk, tools, options) {
|
|
819
|
+
const { prompt, systemPrompt: fullSystem } = formatConversation(
|
|
820
|
+
systemPrompt,
|
|
821
|
+
messages,
|
|
822
|
+
tools
|
|
823
|
+
);
|
|
824
|
+
return new Promise((resolve, reject) => {
|
|
825
|
+
const args = [
|
|
826
|
+
"--print",
|
|
827
|
+
"--output-format",
|
|
828
|
+
"stream-json",
|
|
829
|
+
"--system-prompt",
|
|
830
|
+
fullSystem
|
|
831
|
+
];
|
|
832
|
+
if (model) {
|
|
833
|
+
args.push("--model", model);
|
|
834
|
+
}
|
|
835
|
+
if (options?.maxOutputTokens) {
|
|
836
|
+
args.push("--max-tokens", String(options.maxOutputTokens));
|
|
837
|
+
}
|
|
838
|
+
const proc = spawn("claude", args, {
|
|
839
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
840
|
+
});
|
|
841
|
+
let fullText = "";
|
|
842
|
+
let buffer = "";
|
|
843
|
+
let stderrOutput = "";
|
|
844
|
+
proc.stdin.write(prompt);
|
|
845
|
+
proc.stdin.end();
|
|
846
|
+
proc.stdout.on("data", (data) => {
|
|
847
|
+
buffer += data.toString();
|
|
848
|
+
const lines = buffer.split("\n");
|
|
849
|
+
buffer = lines.pop() || "";
|
|
850
|
+
for (const line of lines) {
|
|
851
|
+
if (!line.trim()) continue;
|
|
852
|
+
try {
|
|
853
|
+
const event = JSON.parse(line);
|
|
854
|
+
if (event.type === "assistant") {
|
|
855
|
+
if (event.subtype === "text" && event.content) {
|
|
856
|
+
fullText += event.content;
|
|
857
|
+
onChunk({ type: "text", text: event.content });
|
|
858
|
+
}
|
|
859
|
+
} else if (event.type === "content_block_delta") {
|
|
860
|
+
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
861
|
+
fullText += event.delta.text;
|
|
862
|
+
onChunk({ type: "text", text: event.delta.text });
|
|
863
|
+
}
|
|
864
|
+
} else if (event.type === "message" && event.message?.content) {
|
|
865
|
+
for (const block of event.message.content) {
|
|
866
|
+
if (block.type === "text" && block.text) {
|
|
867
|
+
fullText += block.text;
|
|
868
|
+
onChunk({ type: "text", text: block.text });
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
} catch {
|
|
873
|
+
if (line.trim()) {
|
|
874
|
+
fullText += line;
|
|
875
|
+
onChunk({ type: "text", text: line });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
proc.stderr.on("data", (data) => {
|
|
881
|
+
stderrOutput += data.toString();
|
|
882
|
+
});
|
|
883
|
+
proc.on("close", (code) => {
|
|
884
|
+
if (buffer.trim()) {
|
|
885
|
+
try {
|
|
886
|
+
const event = JSON.parse(buffer);
|
|
887
|
+
if (event.type === "assistant" && event.subtype === "text" && event.content) {
|
|
888
|
+
fullText += event.content;
|
|
889
|
+
onChunk({ type: "text", text: event.content });
|
|
890
|
+
}
|
|
891
|
+
} catch {
|
|
892
|
+
if (buffer.trim()) {
|
|
893
|
+
fullText += buffer;
|
|
894
|
+
onChunk({ type: "text", text: buffer });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
onChunk({ type: "done" });
|
|
899
|
+
if (code !== 0 && !fullText) {
|
|
900
|
+
reject(
|
|
901
|
+
new Error(
|
|
902
|
+
`Claude CLI exited with code ${code}${stderrOutput ? `: ${stderrOutput.trim()}` : ""}`
|
|
903
|
+
)
|
|
904
|
+
);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const hasTools = tools && tools.length > 0;
|
|
908
|
+
if (hasTools) {
|
|
909
|
+
const toolUses = parseToolUses(fullText);
|
|
910
|
+
if (toolUses.length > 0) {
|
|
911
|
+
let cleanText = fullText;
|
|
912
|
+
const stripRegex = /```json\s*\n?\s*\{"tool_use"[\s\S]*?```/g;
|
|
913
|
+
cleanText = cleanText.replace(stripRegex, "").trim();
|
|
914
|
+
const contentBlocks = [];
|
|
915
|
+
if (cleanText) {
|
|
916
|
+
contentBlocks.push({ type: "text", text: cleanText });
|
|
917
|
+
}
|
|
918
|
+
for (const tu of toolUses) {
|
|
919
|
+
contentBlocks.push({
|
|
920
|
+
type: "tool_use",
|
|
921
|
+
id: tu.id,
|
|
922
|
+
name: tu.name,
|
|
923
|
+
input: tu.input
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
resolve({
|
|
927
|
+
message: { role: "assistant", content: contentBlocks },
|
|
928
|
+
toolUses
|
|
929
|
+
});
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
resolve({
|
|
934
|
+
message: { role: "assistant", content: fullText },
|
|
935
|
+
toolUses: []
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
proc.on("error", (err) => {
|
|
939
|
+
if (err.code === "ENOENT") {
|
|
940
|
+
reject(
|
|
941
|
+
new Error(
|
|
942
|
+
"Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code"
|
|
943
|
+
)
|
|
944
|
+
);
|
|
945
|
+
} else {
|
|
946
|
+
reject(err);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/llm/copilot.ts
|
|
955
|
+
import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
|
|
956
|
+
function isCopilotCliInstalled() {
|
|
957
|
+
try {
|
|
958
|
+
execFileSync2("which", ["copilot"], { stdio: "ignore" });
|
|
959
|
+
return true;
|
|
960
|
+
} catch {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function isCopilotCliAuthenticated() {
|
|
965
|
+
try {
|
|
966
|
+
const result = execFileSync2("copilot", ["--version"], {
|
|
967
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
968
|
+
timeout: 5e3
|
|
969
|
+
});
|
|
970
|
+
return result.toString().trim().length > 0;
|
|
971
|
+
} catch {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function extractText2(content) {
|
|
976
|
+
if (typeof content === "string") return content;
|
|
977
|
+
return content.map((block) => {
|
|
978
|
+
if (block.type === "text") return block.text;
|
|
979
|
+
if (block.type === "tool_result")
|
|
980
|
+
return `[Tool result for ${block.tool_use_id}]: ${block.content}`;
|
|
981
|
+
if (block.type === "tool_use") return `[Used tool: ${block.name}]`;
|
|
982
|
+
return "";
|
|
983
|
+
}).filter(Boolean).join("\n");
|
|
984
|
+
}
|
|
985
|
+
function formatConversation2(systemPrompt, messages, tools) {
|
|
986
|
+
const parts = [];
|
|
987
|
+
let fullSystem = systemPrompt;
|
|
988
|
+
if (tools && tools.length > 0) {
|
|
989
|
+
fullSystem += "\n\n## Available Tools\n";
|
|
990
|
+
fullSystem += "You have access to the following tools. To use a tool, respond with a JSON block in this exact format:\n";
|
|
991
|
+
fullSystem += '```json\n{"tool_use": {"id": "call_1", "name": "tool_name", "input": {\u2026}}}\n```\n\n';
|
|
992
|
+
for (const tool of tools) {
|
|
993
|
+
fullSystem += `### ${tool.name}
|
|
994
|
+
${tool.description}
|
|
995
|
+
Parameters: ${JSON.stringify(tool.input_schema)}
|
|
996
|
+
|
|
997
|
+
`;
|
|
998
|
+
}
|
|
999
|
+
fullSystem += "You may include multiple tool_use blocks. After each tool use, you will receive the result and can continue.\n";
|
|
1000
|
+
}
|
|
1001
|
+
if (messages.length > 1) {
|
|
1002
|
+
parts.push("<conversation_history>");
|
|
1003
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
1004
|
+
const msg = messages[i];
|
|
1005
|
+
const role = msg.role === "user" ? "User" : "Assistant";
|
|
1006
|
+
const text3 = extractText2(msg.content);
|
|
1007
|
+
parts.push(`[${role}]: ${text3}`);
|
|
1008
|
+
}
|
|
1009
|
+
parts.push("</conversation_history>\n");
|
|
1010
|
+
}
|
|
1011
|
+
const lastMsg = messages[messages.length - 1];
|
|
1012
|
+
if (lastMsg) {
|
|
1013
|
+
parts.push(extractText2(lastMsg.content));
|
|
1014
|
+
}
|
|
1015
|
+
return { prompt: parts.join("\n"), systemPrompt: fullSystem };
|
|
1016
|
+
}
|
|
1017
|
+
function parseToolUses2(text3) {
|
|
1018
|
+
const toolUses = [];
|
|
1019
|
+
const codeBlockRegex = /```json\s*\n?([\s\S]*?)```/g;
|
|
1020
|
+
let match;
|
|
1021
|
+
while ((match = codeBlockRegex.exec(text3)) !== null) {
|
|
1022
|
+
try {
|
|
1023
|
+
const parsed = JSON.parse(match[1].trim());
|
|
1024
|
+
if (parsed.tool_use) {
|
|
1025
|
+
toolUses.push({
|
|
1026
|
+
id: parsed.tool_use.id || `call_${toolUses.length + 1}`,
|
|
1027
|
+
name: parsed.tool_use.name,
|
|
1028
|
+
input: parsed.tool_use.input || {}
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (toolUses.length === 0) {
|
|
1035
|
+
const inlineRegex = /\{"tool_use"\s*:\s*\{[^}]*"name"\s*:\s*"[^"]+?"[^}]*\}\s*\}/g;
|
|
1036
|
+
while ((match = inlineRegex.exec(text3)) !== null) {
|
|
1037
|
+
try {
|
|
1038
|
+
const parsed = JSON.parse(match[0]);
|
|
1039
|
+
if (parsed.tool_use) {
|
|
1040
|
+
toolUses.push({
|
|
1041
|
+
id: parsed.tool_use.id || `call_${toolUses.length + 1}`,
|
|
1042
|
+
name: parsed.tool_use.name,
|
|
1043
|
+
input: parsed.tool_use.input || {}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
} catch {
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return toolUses;
|
|
1051
|
+
}
|
|
1052
|
+
function createCopilotClient(model) {
|
|
1053
|
+
return {
|
|
1054
|
+
async chat(systemPrompt, messages, onChunk, tools, options) {
|
|
1055
|
+
const { prompt, systemPrompt: fullSystem } = formatConversation2(
|
|
1056
|
+
systemPrompt,
|
|
1057
|
+
messages,
|
|
1058
|
+
tools
|
|
1059
|
+
);
|
|
1060
|
+
return new Promise((resolve, reject) => {
|
|
1061
|
+
const args = [
|
|
1062
|
+
"--print",
|
|
1063
|
+
"--output-format",
|
|
1064
|
+
"json",
|
|
1065
|
+
"--silent",
|
|
1066
|
+
"--no-custom-instructions"
|
|
1067
|
+
];
|
|
1068
|
+
if (model) {
|
|
1069
|
+
args.push("--model", model);
|
|
1070
|
+
}
|
|
1071
|
+
args.push(prompt);
|
|
1072
|
+
const proc = spawn2("copilot", args, {
|
|
1073
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1074
|
+
env: {
|
|
1075
|
+
...process.env,
|
|
1076
|
+
COPILOT_SYSTEM_PROMPT: fullSystem
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
let fullText = "";
|
|
1080
|
+
let buffer = "";
|
|
1081
|
+
let stderrOutput = "";
|
|
1082
|
+
proc.stdout.on("data", (data) => {
|
|
1083
|
+
buffer += data.toString();
|
|
1084
|
+
const lines = buffer.split("\n");
|
|
1085
|
+
buffer = lines.pop() || "";
|
|
1086
|
+
for (const line of lines) {
|
|
1087
|
+
if (!line.trim()) continue;
|
|
1088
|
+
try {
|
|
1089
|
+
const event = JSON.parse(line);
|
|
1090
|
+
if (event.type === "assistant" && event.content) {
|
|
1091
|
+
fullText += event.content;
|
|
1092
|
+
onChunk({ type: "text", text: event.content });
|
|
1093
|
+
} else if (event.type === "message" && event.message?.content) {
|
|
1094
|
+
for (const block of event.message.content) {
|
|
1095
|
+
if (block.type === "text" && block.text) {
|
|
1096
|
+
fullText += block.text;
|
|
1097
|
+
onChunk({ type: "text", text: block.text });
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
} else if (event.type === "content_block_delta") {
|
|
1101
|
+
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
1102
|
+
fullText += event.delta.text;
|
|
1103
|
+
onChunk({ type: "text", text: event.delta.text });
|
|
1104
|
+
}
|
|
1105
|
+
} else if (event.role === "assistant" && event.content) {
|
|
1106
|
+
const text3 = typeof event.content === "string" ? event.content : event.content.map((b) => b.text || "").join("");
|
|
1107
|
+
if (text3) {
|
|
1108
|
+
fullText += text3;
|
|
1109
|
+
onChunk({ type: "text", text: text3 });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
if (line.trim()) {
|
|
1114
|
+
fullText += line;
|
|
1115
|
+
onChunk({ type: "text", text: line });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
proc.stderr.on("data", (data) => {
|
|
1121
|
+
stderrOutput += data.toString();
|
|
1122
|
+
});
|
|
1123
|
+
proc.on("close", (code) => {
|
|
1124
|
+
if (buffer.trim()) {
|
|
1125
|
+
try {
|
|
1126
|
+
const event = JSON.parse(buffer);
|
|
1127
|
+
if (event.type === "assistant" && event.content) {
|
|
1128
|
+
fullText += event.content;
|
|
1129
|
+
onChunk({ type: "text", text: event.content });
|
|
1130
|
+
} else if (event.role === "assistant" && typeof event.content === "string") {
|
|
1131
|
+
fullText += event.content;
|
|
1132
|
+
onChunk({ type: "text", text: event.content });
|
|
1133
|
+
}
|
|
1134
|
+
} catch {
|
|
1135
|
+
if (buffer.trim()) {
|
|
1136
|
+
fullText += buffer;
|
|
1137
|
+
onChunk({ type: "text", text: buffer });
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
onChunk({ type: "done" });
|
|
1142
|
+
if (code !== 0 && !fullText) {
|
|
1143
|
+
reject(
|
|
1144
|
+
new Error(
|
|
1145
|
+
`Copilot CLI exited with code ${code}${stderrOutput ? `: ${stderrOutput.trim()}` : ""}`
|
|
1146
|
+
)
|
|
1147
|
+
);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const hasTools = tools && tools.length > 0;
|
|
1151
|
+
if (hasTools) {
|
|
1152
|
+
const toolUses = parseToolUses2(fullText);
|
|
1153
|
+
if (toolUses.length > 0) {
|
|
1154
|
+
let cleanText = fullText;
|
|
1155
|
+
const stripRegex = /```json\s*\n?\s*\{"tool_use"[\s\S]*?```/g;
|
|
1156
|
+
cleanText = cleanText.replace(stripRegex, "").trim();
|
|
1157
|
+
const contentBlocks = [];
|
|
1158
|
+
if (cleanText) {
|
|
1159
|
+
contentBlocks.push({ type: "text", text: cleanText });
|
|
1160
|
+
}
|
|
1161
|
+
for (const tu of toolUses) {
|
|
1162
|
+
contentBlocks.push({
|
|
1163
|
+
type: "tool_use",
|
|
1164
|
+
id: tu.id,
|
|
1165
|
+
name: tu.name,
|
|
1166
|
+
input: tu.input
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
resolve({
|
|
1170
|
+
message: { role: "assistant", content: contentBlocks },
|
|
1171
|
+
toolUses
|
|
1172
|
+
});
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
resolve({
|
|
1177
|
+
message: { role: "assistant", content: fullText },
|
|
1178
|
+
toolUses: []
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
proc.on("error", (err) => {
|
|
1182
|
+
if (err.code === "ENOENT") {
|
|
1183
|
+
reject(
|
|
1184
|
+
new Error(
|
|
1185
|
+
"Copilot CLI not found. Install it from: https://docs.github.com/copilot/how-tos/copilot-cli"
|
|
1186
|
+
)
|
|
1187
|
+
);
|
|
1188
|
+
} else {
|
|
1189
|
+
reject(err);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
729
1197
|
// src/mcp/client.ts
|
|
730
1198
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
731
1199
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -931,7 +1399,7 @@ import logUpdate from "log-update";
|
|
|
931
1399
|
import fs12 from "fs";
|
|
932
1400
|
import path12 from "path";
|
|
933
1401
|
import os11 from "os";
|
|
934
|
-
import { execFileSync } from "child_process";
|
|
1402
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
935
1403
|
import pc5 from "picocolors";
|
|
936
1404
|
|
|
937
1405
|
// src/layers/parsers.ts
|
|
@@ -3487,6 +3955,7 @@ function handleHelp() {
|
|
|
3487
3955
|
` ${pc5.cyan("/profile me")} View your profile`,
|
|
3488
3956
|
` ${pc5.cyan("/profile edit")} Edit your profile`,
|
|
3489
3957
|
` ${pc5.cyan("/profile")} List agent profiles`,
|
|
3958
|
+
` ${pc5.cyan("/showcase")} Browse & switch companion templates`,
|
|
3490
3959
|
` ${pc5.cyan("/delegate")} Delegate tasks to sub-agents`,
|
|
3491
3960
|
` ${pc5.cyan("/team")} Manage agent teams`,
|
|
3492
3961
|
` ${pc5.cyan("/update")} Check for updates`,
|
|
@@ -3553,8 +4022,8 @@ function handleReset(action) {
|
|
|
3553
4022
|
}
|
|
3554
4023
|
function handleUpdate() {
|
|
3555
4024
|
try {
|
|
3556
|
-
const current =
|
|
3557
|
-
const local = true ? "0.
|
|
4025
|
+
const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
4026
|
+
const local = true ? "0.21.1" : "unknown";
|
|
3558
4027
|
if (current === local) {
|
|
3559
4028
|
return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
|
|
3560
4029
|
}
|
|
@@ -4123,6 +4592,103 @@ async function handleReminderCommand(action, args) {
|
|
|
4123
4592
|
}
|
|
4124
4593
|
return { handled: true, output: pc5.yellow(`Unknown action: /reminder ${action}. Try /reminder --help`) };
|
|
4125
4594
|
}
|
|
4595
|
+
function handleShowcaseCommand(action, args) {
|
|
4596
|
+
const showcases = loadShowcaseManifest();
|
|
4597
|
+
if (showcases.length === 0) {
|
|
4598
|
+
return {
|
|
4599
|
+
handled: true,
|
|
4600
|
+
output: pc5.dim("No showcase templates found.") + `
|
|
4601
|
+
|
|
4602
|
+
Install aman-showcase to get 13 pre-built companion personalities:
|
|
4603
|
+
${pc5.bold("npm install -g @aman_asmuei/aman-showcase")}
|
|
4604
|
+
Or place it as a sibling directory to aman-agent.`
|
|
4605
|
+
};
|
|
4606
|
+
}
|
|
4607
|
+
const corePath = path12.join(os11.homedir(), ".acore", "core.md");
|
|
4608
|
+
let currentShowcase = null;
|
|
4609
|
+
if (fs12.existsSync(corePath)) {
|
|
4610
|
+
const content = fs12.readFileSync(corePath, "utf-8");
|
|
4611
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
4612
|
+
if (nameMatch) {
|
|
4613
|
+
const coreName = nameMatch[1].trim().toLowerCase();
|
|
4614
|
+
const match = showcases.find((s) => coreName.includes(s.name) || coreName.includes(s.title.split("\u2014")[0].trim().toLowerCase()));
|
|
4615
|
+
if (match) currentShowcase = match.name;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
if (!action || action === "list") {
|
|
4619
|
+
const lines = showcases.map((s) => {
|
|
4620
|
+
const active = s.name === currentShowcase ? pc5.green(" \u2190 active") : "";
|
|
4621
|
+
const langBadge = s.language === "ms" ? " [BM]" : s.language === "en+ms" ? " [EN/BM]" : "";
|
|
4622
|
+
return ` ${pc5.bold(s.name.padEnd(12))} ${s.title}${langBadge}${active}`;
|
|
4623
|
+
});
|
|
4624
|
+
const currentLine = currentShowcase ? `
|
|
4625
|
+
Current: ${pc5.bold(currentShowcase)}
|
|
4626
|
+
` : `
|
|
4627
|
+
No showcase active (using default personality)
|
|
4628
|
+
`;
|
|
4629
|
+
return {
|
|
4630
|
+
handled: true,
|
|
4631
|
+
output: `Showcase templates (${showcases.length}):
|
|
4632
|
+
|
|
4633
|
+
${lines.join("\n")}
|
|
4634
|
+
${currentLine}
|
|
4635
|
+
${pc5.dim("Switch with: /showcase install <name>")}`
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
if (action === "install" || action === "switch" || action === "use") {
|
|
4639
|
+
const name = args[0];
|
|
4640
|
+
if (!name) {
|
|
4641
|
+
return { handled: true, output: pc5.yellow("Usage: /showcase install <name>\n\nRun /showcase list to see available templates.") };
|
|
4642
|
+
}
|
|
4643
|
+
const entry = showcases.find((s) => s.name === name);
|
|
4644
|
+
if (!entry) {
|
|
4645
|
+
return { handled: true, output: pc5.red(`Showcase not found: ${name}`) + `
|
|
4646
|
+
|
|
4647
|
+
Available: ${showcases.map((s) => s.name).join(", ")}` };
|
|
4648
|
+
}
|
|
4649
|
+
if (name === currentShowcase) {
|
|
4650
|
+
return { handled: true, output: pc5.dim(`${entry.title} is already active.`) };
|
|
4651
|
+
}
|
|
4652
|
+
try {
|
|
4653
|
+
const result = installShowcaseTemplate(name);
|
|
4654
|
+
const lines = [pc5.green(`Installed ${pc5.bold(entry.title)}`)];
|
|
4655
|
+
for (const f of result.installed) {
|
|
4656
|
+
lines.push(pc5.dim(` ${f}`));
|
|
4657
|
+
}
|
|
4658
|
+
if (result.backed_up.length > 0) {
|
|
4659
|
+
lines.push(pc5.dim(`
|
|
4660
|
+
Backed up ${result.backed_up.length} existing file(s) (.bak)`));
|
|
4661
|
+
}
|
|
4662
|
+
lines.push("");
|
|
4663
|
+
lines.push(pc5.yellow("Restart aman-agent to use the new personality."));
|
|
4664
|
+
lines.push(pc5.dim("Your user profile (/profile me) is unchanged \u2014 only the AI personality switched."));
|
|
4665
|
+
return { handled: true, output: lines.join("\n") };
|
|
4666
|
+
} catch (err) {
|
|
4667
|
+
return { handled: true, output: pc5.red(`Failed to install: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
if (action === "current") {
|
|
4671
|
+
if (currentShowcase) {
|
|
4672
|
+
const entry = showcases.find((s) => s.name === currentShowcase);
|
|
4673
|
+
return { handled: true, output: `Active showcase: ${pc5.bold(entry?.title || currentShowcase)}
|
|
4674
|
+
${pc5.dim(entry?.description || "")}` };
|
|
4675
|
+
}
|
|
4676
|
+
return { handled: true, output: pc5.dim("No showcase active \u2014 using default personality.") + `
|
|
4677
|
+
${pc5.dim("Install one with: /showcase install <name>")}` };
|
|
4678
|
+
}
|
|
4679
|
+
if (action === "help") {
|
|
4680
|
+
return { handled: true, output: `Showcase commands:
|
|
4681
|
+
|
|
4682
|
+
/showcase List all available templates
|
|
4683
|
+
/showcase install <n> Install/switch to a template
|
|
4684
|
+
/showcase current Show active template
|
|
4685
|
+
|
|
4686
|
+
${pc5.dim("Showcase templates replace your AI's personality, workflows, rules, and skills.")}
|
|
4687
|
+
${pc5.dim("Your user profile (/profile me) stays unchanged \u2014 only the AI personality switches.")}
|
|
4688
|
+
${pc5.dim("Existing files are backed up (.bak) before overwriting.")}` };
|
|
4689
|
+
}
|
|
4690
|
+
return { handled: true, output: pc5.yellow(`Unknown action: /showcase ${action}. Try /showcase help`) };
|
|
4691
|
+
}
|
|
4126
4692
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
4127
4693
|
"quit",
|
|
4128
4694
|
"exit",
|
|
@@ -4151,7 +4717,8 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
4151
4717
|
"plan",
|
|
4152
4718
|
"profile",
|
|
4153
4719
|
"delegate",
|
|
4154
|
-
"team"
|
|
4720
|
+
"team",
|
|
4721
|
+
"showcase"
|
|
4155
4722
|
]);
|
|
4156
4723
|
async function handleCommand(input, ctx) {
|
|
4157
4724
|
const trimmed = input.trim();
|
|
@@ -4208,6 +4775,8 @@ async function handleCommand(input, ctx) {
|
|
|
4208
4775
|
return handleTeamCommand(action, args, ctx);
|
|
4209
4776
|
case "reminder":
|
|
4210
4777
|
return handleReminderCommand(action, args);
|
|
4778
|
+
case "showcase":
|
|
4779
|
+
return handleShowcaseCommand(action, args);
|
|
4211
4780
|
case "update":
|
|
4212
4781
|
case "upgrade":
|
|
4213
4782
|
return handleUpdate();
|
|
@@ -5909,7 +6478,7 @@ function bootstrapEcosystem() {
|
|
|
5909
6478
|
return true;
|
|
5910
6479
|
}
|
|
5911
6480
|
var program = new Command();
|
|
5912
|
-
program.name("aman-agent").description("Your AI companion, running locally").version("0.
|
|
6481
|
+
program.name("aman-agent").description("Your AI companion, running locally").version("0.21.1").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).option("--profile <name>", "Use a specific agent profile (e.g., coder, writer, researcher)").action(async (options) => {
|
|
5913
6482
|
p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
|
|
5914
6483
|
let config = loadConfig();
|
|
5915
6484
|
if (!config) {
|
|
@@ -5926,14 +6495,19 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
5926
6495
|
message: "LLM provider",
|
|
5927
6496
|
options: [
|
|
5928
6497
|
{
|
|
5929
|
-
value: "
|
|
6498
|
+
value: "claude-code",
|
|
5930
6499
|
label: "Claude (Anthropic)",
|
|
5931
6500
|
hint: "recommended"
|
|
5932
6501
|
},
|
|
6502
|
+
{
|
|
6503
|
+
value: "copilot",
|
|
6504
|
+
label: "GitHub Copilot",
|
|
6505
|
+
hint: "uses GitHub Models"
|
|
6506
|
+
},
|
|
5933
6507
|
{ value: "openai", label: "GPT (OpenAI)" },
|
|
5934
6508
|
{ value: "ollama", label: "Ollama (local)", hint: "free, runs offline" }
|
|
5935
6509
|
],
|
|
5936
|
-
initialValue: "
|
|
6510
|
+
initialValue: "claude-code"
|
|
5937
6511
|
});
|
|
5938
6512
|
if (p3.isCancel(provider)) process.exit(0);
|
|
5939
6513
|
let apiKey = "";
|
|
@@ -5947,20 +6521,58 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
5947
6521
|
});
|
|
5948
6522
|
if (p3.isCancel(modelInput)) process.exit(0);
|
|
5949
6523
|
defaultModel = modelInput || "llama3.2";
|
|
5950
|
-
} else if (provider === "
|
|
5951
|
-
p3.
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6524
|
+
} else if (provider === "claude-code") {
|
|
6525
|
+
p3.note(
|
|
6526
|
+
[
|
|
6527
|
+
`${pc8.bold("Claude Plans:")}`,
|
|
6528
|
+
"",
|
|
6529
|
+
` ${pc8.cyan("Free")} $0/mo Basic access`,
|
|
6530
|
+
` ${pc8.cyan("Pro")} $20/mo Full models + Claude Code`,
|
|
6531
|
+
` ${pc8.cyan("Max 5x")} $100/mo 5\xD7 usage + Claude Code`,
|
|
6532
|
+
` ${pc8.cyan("Max 20x")} $200/mo 20\xD7 usage + Opus 4.6 + 1M context`,
|
|
6533
|
+
` ${pc8.cyan("Team")} $25+/seat Collaborative workspace`,
|
|
6534
|
+
` ${pc8.cyan("Enterprise")} Custom SSO, admin, dedicated support`,
|
|
6535
|
+
"",
|
|
6536
|
+
`${pc8.dim("Authentication is handled by Claude Code CLI.")}`,
|
|
6537
|
+
`${pc8.dim("Supports: subscription, API billing, Bedrock, Vertex AI.")}`
|
|
6538
|
+
].join("\n"),
|
|
6539
|
+
"Claude Plans"
|
|
6540
|
+
);
|
|
6541
|
+
if (!isClaudeCliInstalled()) {
|
|
6542
|
+
p3.log.error("Claude Code CLI is not installed.");
|
|
6543
|
+
p3.log.info("Install it with:");
|
|
6544
|
+
p3.log.step(pc8.bold("npm install -g @anthropic-ai/claude-code"));
|
|
6545
|
+
p3.log.info(pc8.dim("Then re-run aman-agent to continue setup."));
|
|
6546
|
+
process.exit(1);
|
|
6547
|
+
}
|
|
6548
|
+
p3.log.success("Claude Code CLI detected.");
|
|
6549
|
+
const authAction = await p3.select({
|
|
6550
|
+
message: "Authentication",
|
|
6551
|
+
options: [
|
|
6552
|
+
{ value: "logged-in", label: "Already logged in to Claude Code" },
|
|
6553
|
+
{ value: "login", label: "Log in now", hint: "runs: claude login" }
|
|
6554
|
+
]
|
|
5956
6555
|
});
|
|
5957
|
-
if (p3.isCancel(
|
|
6556
|
+
if (p3.isCancel(authAction)) process.exit(0);
|
|
6557
|
+
if (authAction === "login") {
|
|
6558
|
+
p3.log.step("Launching Claude Code login...");
|
|
6559
|
+
const { spawnSync } = await import("child_process");
|
|
6560
|
+
const loginResult = spawnSync("claude", ["login"], {
|
|
6561
|
+
stdio: "inherit"
|
|
6562
|
+
});
|
|
6563
|
+
if (loginResult.status !== 0) {
|
|
6564
|
+
p3.log.error("Login failed or was cancelled. Please try again.");
|
|
6565
|
+
process.exit(1);
|
|
6566
|
+
}
|
|
6567
|
+
p3.log.success("Login successful.");
|
|
6568
|
+
}
|
|
6569
|
+
apiKey = "claude-code";
|
|
5958
6570
|
const modelChoice = await p3.select({
|
|
5959
6571
|
message: "Claude model",
|
|
5960
6572
|
options: [
|
|
5961
6573
|
{ value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", hint: "fast, recommended" },
|
|
5962
6574
|
{ value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "most capable" },
|
|
5963
|
-
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "fastest
|
|
6575
|
+
{ value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "fastest" },
|
|
5964
6576
|
{ value: "custom", label: "Custom model ID" }
|
|
5965
6577
|
],
|
|
5966
6578
|
initialValue: "claude-sonnet-4-6"
|
|
@@ -5977,6 +6589,82 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
5977
6589
|
} else {
|
|
5978
6590
|
defaultModel = modelChoice;
|
|
5979
6591
|
}
|
|
6592
|
+
} else if (provider === "copilot") {
|
|
6593
|
+
p3.note(
|
|
6594
|
+
[
|
|
6595
|
+
`${pc8.bold("GitHub Copilot Plans:")}`,
|
|
6596
|
+
"",
|
|
6597
|
+
` ${pc8.cyan("Free")} $0/mo 2,000 code completions + 50 chat msgs`,
|
|
6598
|
+
` ${pc8.cyan("Pro")} $10/mo Unlimited completions + chat`,
|
|
6599
|
+
` ${pc8.cyan("Pro+")} $39/mo Unlimited + Opus/o1 + agent mode`,
|
|
6600
|
+
` ${pc8.cyan("Business")} $19/user/mo Team admin + policy controls`,
|
|
6601
|
+
` ${pc8.cyan("Enterprise")} $39/user/mo SSO, audit logs, IP indemnity`,
|
|
6602
|
+
"",
|
|
6603
|
+
`${pc8.dim("Authentication is handled by the Copilot CLI.")}`,
|
|
6604
|
+
`${pc8.dim("Subscribe at: https://github.com/features/copilot")}`
|
|
6605
|
+
].join("\n"),
|
|
6606
|
+
"Copilot Plans"
|
|
6607
|
+
);
|
|
6608
|
+
if (!isCopilotCliInstalled()) {
|
|
6609
|
+
p3.log.error("Copilot CLI is not installed.");
|
|
6610
|
+
p3.log.info("Install it from:");
|
|
6611
|
+
p3.log.step(pc8.bold("https://docs.github.com/copilot/how-tos/copilot-cli"));
|
|
6612
|
+
p3.log.info(pc8.dim("Then re-run aman-agent to continue setup."));
|
|
6613
|
+
process.exit(1);
|
|
6614
|
+
}
|
|
6615
|
+
p3.log.success("Copilot CLI detected.");
|
|
6616
|
+
const copilotAuth = isCopilotCliAuthenticated();
|
|
6617
|
+
if (copilotAuth) {
|
|
6618
|
+
p3.log.success("Copilot authentication found.");
|
|
6619
|
+
} else {
|
|
6620
|
+
p3.log.warn("Not logged in to Copilot.");
|
|
6621
|
+
const authAction = await p3.select({
|
|
6622
|
+
message: "Authentication",
|
|
6623
|
+
options: [
|
|
6624
|
+
{ value: "login", label: "Log in now", hint: "runs: copilot login" },
|
|
6625
|
+
{ value: "skip", label: "Skip (I'll log in later)" }
|
|
6626
|
+
]
|
|
6627
|
+
});
|
|
6628
|
+
if (p3.isCancel(authAction)) process.exit(0);
|
|
6629
|
+
if (authAction === "login") {
|
|
6630
|
+
p3.log.step("Launching Copilot login...");
|
|
6631
|
+
const { spawnSync } = await import("child_process");
|
|
6632
|
+
const loginResult = spawnSync("copilot", ["login"], {
|
|
6633
|
+
stdio: "inherit"
|
|
6634
|
+
});
|
|
6635
|
+
if (loginResult.status !== 0) {
|
|
6636
|
+
p3.log.error("Login failed or was cancelled.");
|
|
6637
|
+
process.exit(1);
|
|
6638
|
+
}
|
|
6639
|
+
p3.log.success("Copilot login successful.");
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
apiKey = "copilot";
|
|
6643
|
+
const modelChoice = await p3.select({
|
|
6644
|
+
message: "Model",
|
|
6645
|
+
options: [
|
|
6646
|
+
{ value: "default", label: "Default", hint: "Copilot's default model" },
|
|
6647
|
+
{ value: "gpt-4o", label: "GPT-4o", hint: "fast" },
|
|
6648
|
+
{ value: "gpt-5.2", label: "GPT-5.2", hint: "most capable" },
|
|
6649
|
+
{ value: "o3-mini", label: "o3-mini", hint: "reasoning" },
|
|
6650
|
+
{ value: "custom", label: "Custom model ID" }
|
|
6651
|
+
],
|
|
6652
|
+
initialValue: "default"
|
|
6653
|
+
});
|
|
6654
|
+
if (p3.isCancel(modelChoice)) process.exit(0);
|
|
6655
|
+
if (modelChoice === "custom") {
|
|
6656
|
+
const customModel = await p3.text({
|
|
6657
|
+
message: "Model ID (run copilot --help for available models)",
|
|
6658
|
+
placeholder: "gpt-4o",
|
|
6659
|
+
validate: (v) => v.length === 0 ? "Model ID is required" : void 0
|
|
6660
|
+
});
|
|
6661
|
+
if (p3.isCancel(customModel)) process.exit(0);
|
|
6662
|
+
defaultModel = customModel;
|
|
6663
|
+
} else if (modelChoice === "default") {
|
|
6664
|
+
defaultModel = "";
|
|
6665
|
+
} else {
|
|
6666
|
+
defaultModel = modelChoice;
|
|
6667
|
+
}
|
|
5980
6668
|
} else {
|
|
5981
6669
|
apiKey = await p3.text({
|
|
5982
6670
|
message: "API key",
|
|
@@ -6093,7 +6781,11 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
6093
6781
|
input_schema: t.input_schema
|
|
6094
6782
|
}));
|
|
6095
6783
|
let client;
|
|
6096
|
-
if (config.provider === "
|
|
6784
|
+
if (config.provider === "claude-code") {
|
|
6785
|
+
client = createClaudeCodeClient(model);
|
|
6786
|
+
} else if (config.provider === "copilot") {
|
|
6787
|
+
client = createCopilotClient(model);
|
|
6788
|
+
} else if (config.provider === "anthropic") {
|
|
6097
6789
|
client = createAnthropicClient(config.apiKey, model);
|
|
6098
6790
|
} else if (config.provider === "ollama") {
|
|
6099
6791
|
client = createOllamaClient(model);
|