@aman_asmuei/aman-agent 0.7.7 → 0.16.2
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 +508 -27
- package/dist/index.js +2621 -353
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as p2 from "@clack/prompts";
|
|
4
|
-
import
|
|
4
|
+
import pc7 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs from "fs";
|
|
@@ -15,7 +15,8 @@ var DEFAULT_HOOKS = {
|
|
|
15
15
|
evalPrompt: true,
|
|
16
16
|
autoSessionSave: true,
|
|
17
17
|
extractMemories: true,
|
|
18
|
-
featureHints: true
|
|
18
|
+
featureHints: true,
|
|
19
|
+
personalityAdapt: true
|
|
19
20
|
};
|
|
20
21
|
var CONFIG_DIR = path.join(os.homedir(), ".aman-agent");
|
|
21
22
|
var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
@@ -96,18 +97,35 @@ function buildBudgetedPrompt(components, maxTokens = 8e3) {
|
|
|
96
97
|
|
|
97
98
|
// src/prompt.ts
|
|
98
99
|
var ECOSYSTEM_FILES = [
|
|
99
|
-
{ name: "identity", dir: ".acore", file: "core.md" },
|
|
100
|
+
{ name: "identity", dir: ".acore", file: "core.md", profileOverridable: true },
|
|
100
101
|
{ name: "tools", dir: ".akit", file: "kit.md" },
|
|
101
102
|
{ name: "workflows", dir: ".aflow", file: "flow.md" },
|
|
102
|
-
{ name: "guardrails", dir: ".arules", file: "rules.md" },
|
|
103
|
-
{ name: "skills", dir: ".askill", file: "skills.md" }
|
|
103
|
+
{ name: "guardrails", dir: ".arules", file: "rules.md", profileOverridable: true },
|
|
104
|
+
{ name: "skills", dir: ".askill", file: "skills.md", profileOverridable: true }
|
|
104
105
|
];
|
|
105
|
-
function
|
|
106
|
+
function resolveLayerPath(entry, home2, profile) {
|
|
107
|
+
if (profile && entry.profileOverridable) {
|
|
108
|
+
const profilePath = path2.join(home2, ".acore", "profiles", profile, entry.file);
|
|
109
|
+
if (fs2.existsSync(profilePath)) return profilePath;
|
|
110
|
+
if (entry.name === "guardrails") {
|
|
111
|
+
const altPath = path2.join(home2, ".acore", "profiles", profile, "rules.md");
|
|
112
|
+
if (fs2.existsSync(altPath)) return altPath;
|
|
113
|
+
}
|
|
114
|
+
if (entry.name === "skills") {
|
|
115
|
+
const altPath = path2.join(home2, ".acore", "profiles", profile, "skills.md");
|
|
116
|
+
if (fs2.existsSync(altPath)) return altPath;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const globalPath = path2.join(home2, entry.dir, entry.file);
|
|
120
|
+
if (fs2.existsSync(globalPath)) return globalPath;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function assembleSystemPrompt(maxTokens, profile) {
|
|
106
124
|
const home2 = os2.homedir();
|
|
107
125
|
const components = [];
|
|
108
126
|
for (const entry of ECOSYSTEM_FILES) {
|
|
109
|
-
const filePath =
|
|
110
|
-
if (
|
|
127
|
+
const filePath = resolveLayerPath(entry, home2, profile);
|
|
128
|
+
if (filePath) {
|
|
111
129
|
const content = fs2.readFileSync(filePath, "utf-8").trim();
|
|
112
130
|
components.push({
|
|
113
131
|
name: entry.name,
|
|
@@ -130,9 +148,43 @@ function assembleSystemPrompt(maxTokens) {
|
|
|
130
148
|
prompt: budgeted.prompt,
|
|
131
149
|
layers: budgeted.included,
|
|
132
150
|
truncated: budgeted.truncated,
|
|
133
|
-
totalTokens: budgeted.totalTokens
|
|
151
|
+
totalTokens: budgeted.totalTokens,
|
|
152
|
+
profile
|
|
134
153
|
};
|
|
135
154
|
}
|
|
155
|
+
function listProfiles() {
|
|
156
|
+
const profilesDir = path2.join(os2.homedir(), ".acore", "profiles");
|
|
157
|
+
if (!fs2.existsSync(profilesDir)) return [];
|
|
158
|
+
const profiles = [];
|
|
159
|
+
for (const entry of fs2.readdirSync(profilesDir, { withFileTypes: true })) {
|
|
160
|
+
if (!entry.isDirectory()) continue;
|
|
161
|
+
const corePath = path2.join(profilesDir, entry.name, "core.md");
|
|
162
|
+
if (!fs2.existsSync(corePath)) continue;
|
|
163
|
+
const content = fs2.readFileSync(corePath, "utf-8");
|
|
164
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
165
|
+
const personalityMatch = content.match(/- Personality:\s*(.+)/);
|
|
166
|
+
profiles.push({
|
|
167
|
+
name: entry.name,
|
|
168
|
+
aiName: nameMatch?.[1]?.trim() || entry.name,
|
|
169
|
+
personality: personalityMatch?.[1]?.trim() || "default"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return profiles;
|
|
173
|
+
}
|
|
174
|
+
function getProfileAiName(profile) {
|
|
175
|
+
const home2 = os2.homedir();
|
|
176
|
+
let corePath;
|
|
177
|
+
if (profile) {
|
|
178
|
+
const profileCorePath = path2.join(home2, ".acore", "profiles", profile, "core.md");
|
|
179
|
+
corePath = fs2.existsSync(profileCorePath) ? profileCorePath : path2.join(home2, ".acore", "core.md");
|
|
180
|
+
} else {
|
|
181
|
+
corePath = path2.join(home2, ".acore", "core.md");
|
|
182
|
+
}
|
|
183
|
+
if (!fs2.existsSync(corePath)) return "Assistant";
|
|
184
|
+
const content = fs2.readFileSync(corePath, "utf-8");
|
|
185
|
+
const match = content.match(/^# (.+)$/m);
|
|
186
|
+
return match?.[1]?.trim() || "Assistant";
|
|
187
|
+
}
|
|
136
188
|
|
|
137
189
|
// src/llm/anthropic.ts
|
|
138
190
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -147,6 +199,16 @@ function toAnthropicMessages(messages) {
|
|
|
147
199
|
if (block.type === "text") {
|
|
148
200
|
return { type: "text", text: block.text };
|
|
149
201
|
}
|
|
202
|
+
if (block.type === "image") {
|
|
203
|
+
return {
|
|
204
|
+
type: "image",
|
|
205
|
+
source: {
|
|
206
|
+
type: "base64",
|
|
207
|
+
media_type: block.source.media_type,
|
|
208
|
+
data: block.source.data
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
150
212
|
if (block.type === "tool_use") {
|
|
151
213
|
return {
|
|
152
214
|
type: "tool_use",
|
|
@@ -311,8 +373,26 @@ function toOpenAIMessages(systemPrompt, messages) {
|
|
|
311
373
|
}
|
|
312
374
|
}
|
|
313
375
|
} else {
|
|
314
|
-
const
|
|
315
|
-
|
|
376
|
+
const hasImages = m.content.some((b) => b.type === "image");
|
|
377
|
+
if (hasImages) {
|
|
378
|
+
const parts = [];
|
|
379
|
+
for (const b of m.content) {
|
|
380
|
+
if (b.type === "text") {
|
|
381
|
+
parts.push({ type: "text", text: b.text });
|
|
382
|
+
} else if (b.type === "image") {
|
|
383
|
+
parts.push({
|
|
384
|
+
type: "image_url",
|
|
385
|
+
image_url: {
|
|
386
|
+
url: `data:${b.source.media_type};base64,${b.source.data}`
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
result.push({ role: "user", content: parts });
|
|
392
|
+
} else {
|
|
393
|
+
const text2 = m.content.map((b) => "text" in b ? b.text : "").join("");
|
|
394
|
+
result.push({ role: "user", content: text2 });
|
|
395
|
+
}
|
|
316
396
|
}
|
|
317
397
|
}
|
|
318
398
|
}
|
|
@@ -415,6 +495,74 @@ function createOpenAIClient(apiKey, model) {
|
|
|
415
495
|
|
|
416
496
|
// src/llm/ollama.ts
|
|
417
497
|
import OpenAI2 from "openai";
|
|
498
|
+
function toOllamaMessages(systemPrompt, messages) {
|
|
499
|
+
const result = [
|
|
500
|
+
{ role: "system", content: systemPrompt }
|
|
501
|
+
];
|
|
502
|
+
for (const m of messages) {
|
|
503
|
+
if (typeof m.content === "string") {
|
|
504
|
+
result.push({
|
|
505
|
+
role: m.role,
|
|
506
|
+
content: m.content
|
|
507
|
+
});
|
|
508
|
+
} else if (m.role === "assistant") {
|
|
509
|
+
const textParts = m.content.filter((b) => b.type === "text");
|
|
510
|
+
const toolUseParts = m.content.filter((b) => b.type === "tool_use");
|
|
511
|
+
const text2 = textParts.map((b) => "text" in b ? b.text : "").join("");
|
|
512
|
+
if (toolUseParts.length > 0) {
|
|
513
|
+
result.push({
|
|
514
|
+
role: "assistant",
|
|
515
|
+
content: text2 || null,
|
|
516
|
+
tool_calls: toolUseParts.map((b) => ({
|
|
517
|
+
id: "id" in b ? b.id : "",
|
|
518
|
+
type: "function",
|
|
519
|
+
function: {
|
|
520
|
+
name: "name" in b ? b.name : "",
|
|
521
|
+
arguments: JSON.stringify("input" in b ? b.input : {})
|
|
522
|
+
}
|
|
523
|
+
}))
|
|
524
|
+
});
|
|
525
|
+
} else {
|
|
526
|
+
result.push({ role: "assistant", content: text2 });
|
|
527
|
+
}
|
|
528
|
+
} else if (m.role === "user") {
|
|
529
|
+
const toolResults = m.content.filter((b) => b.type === "tool_result");
|
|
530
|
+
if (toolResults.length > 0) {
|
|
531
|
+
for (const tr of toolResults) {
|
|
532
|
+
if (tr.type === "tool_result") {
|
|
533
|
+
result.push({
|
|
534
|
+
role: "tool",
|
|
535
|
+
tool_call_id: tr.tool_use_id,
|
|
536
|
+
content: tr.content
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const hasImages = m.content.some((b) => b.type === "image");
|
|
542
|
+
if (hasImages) {
|
|
543
|
+
const parts = [];
|
|
544
|
+
for (const b of m.content) {
|
|
545
|
+
if (b.type === "text") {
|
|
546
|
+
parts.push({ type: "text", text: b.text });
|
|
547
|
+
} else if (b.type === "image") {
|
|
548
|
+
parts.push({
|
|
549
|
+
type: "image_url",
|
|
550
|
+
image_url: {
|
|
551
|
+
url: `data:${b.source.media_type};base64,${b.source.data}`
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
result.push({ role: "user", content: parts });
|
|
557
|
+
} else {
|
|
558
|
+
const text2 = m.content.map((b) => "text" in b ? b.text : "").join("");
|
|
559
|
+
result.push({ role: "user", content: text2 });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
418
566
|
function createOllamaClient(model, baseURL) {
|
|
419
567
|
const client = new OpenAI2({
|
|
420
568
|
baseURL: baseURL || "http://localhost:11434/v1",
|
|
@@ -422,28 +570,83 @@ function createOllamaClient(model, baseURL) {
|
|
|
422
570
|
// Ollama doesn't require a real key
|
|
423
571
|
});
|
|
424
572
|
return {
|
|
425
|
-
async chat(systemPrompt, messages, onChunk,
|
|
426
|
-
|
|
573
|
+
async chat(systemPrompt, messages, onChunk, tools) {
|
|
574
|
+
const ollamaMessages = toOllamaMessages(systemPrompt, messages);
|
|
575
|
+
const hasTools = tools && tools.length > 0;
|
|
427
576
|
try {
|
|
428
|
-
|
|
577
|
+
let fullText = "";
|
|
578
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
579
|
+
const createParams = {
|
|
429
580
|
model,
|
|
430
581
|
max_tokens: 8192,
|
|
431
|
-
messages:
|
|
432
|
-
{ role: "system", content: systemPrompt },
|
|
433
|
-
...messages.map((m) => ({
|
|
434
|
-
role: m.role,
|
|
435
|
-
content: typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("")
|
|
436
|
-
}))
|
|
437
|
-
],
|
|
582
|
+
messages: ollamaMessages,
|
|
438
583
|
stream: true
|
|
439
|
-
}
|
|
584
|
+
};
|
|
585
|
+
if (hasTools) {
|
|
586
|
+
createParams.tools = tools.map((t) => ({
|
|
587
|
+
type: "function",
|
|
588
|
+
function: {
|
|
589
|
+
name: t.name,
|
|
590
|
+
description: t.description,
|
|
591
|
+
parameters: t.input_schema
|
|
592
|
+
}
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
const stream = await client.chat.completions.create(
|
|
596
|
+
createParams
|
|
597
|
+
);
|
|
440
598
|
for await (const chunk of stream) {
|
|
441
|
-
const
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
599
|
+
const delta = chunk.choices[0]?.delta;
|
|
600
|
+
if (!delta) continue;
|
|
601
|
+
if (delta.content) {
|
|
602
|
+
fullText += delta.content;
|
|
603
|
+
onChunk({ type: "text", text: delta.content });
|
|
445
604
|
}
|
|
605
|
+
if (delta.tool_calls) {
|
|
606
|
+
for (const tc of delta.tool_calls) {
|
|
607
|
+
const idx = tc.index;
|
|
608
|
+
let acc = toolCallAccumulators.get(idx);
|
|
609
|
+
if (!acc) {
|
|
610
|
+
acc = { id: "", name: "", arguments: "" };
|
|
611
|
+
toolCallAccumulators.set(idx, acc);
|
|
612
|
+
}
|
|
613
|
+
if (tc.id) {
|
|
614
|
+
acc.id = tc.id;
|
|
615
|
+
}
|
|
616
|
+
if (tc.function?.name) {
|
|
617
|
+
acc.name = tc.function.name;
|
|
618
|
+
}
|
|
619
|
+
if (tc.function?.arguments) {
|
|
620
|
+
acc.arguments += tc.function.arguments;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const toolUses = Array.from(toolCallAccumulators.entries()).sort(([a], [b]) => a - b).map(([, acc]) => ({
|
|
626
|
+
id: acc.id,
|
|
627
|
+
name: acc.name,
|
|
628
|
+
input: JSON.parse(acc.arguments || "{}")
|
|
629
|
+
}));
|
|
630
|
+
onChunk({ type: "done" });
|
|
631
|
+
if (toolUses.length > 0) {
|
|
632
|
+
const contentBlocks = [
|
|
633
|
+
...fullText ? [{ type: "text", text: fullText }] : [],
|
|
634
|
+
...toolUses.map((tu) => ({
|
|
635
|
+
type: "tool_use",
|
|
636
|
+
id: tu.id,
|
|
637
|
+
name: tu.name,
|
|
638
|
+
input: tu.input
|
|
639
|
+
}))
|
|
640
|
+
];
|
|
641
|
+
return {
|
|
642
|
+
message: { role: "assistant", content: contentBlocks },
|
|
643
|
+
toolUses
|
|
644
|
+
};
|
|
446
645
|
}
|
|
646
|
+
return {
|
|
647
|
+
message: { role: "assistant", content: fullText },
|
|
648
|
+
toolUses: []
|
|
649
|
+
};
|
|
447
650
|
} catch (error) {
|
|
448
651
|
if (error instanceof Error && error.message.includes("ECONNREFUSED")) {
|
|
449
652
|
throw new Error(
|
|
@@ -452,11 +655,6 @@ function createOllamaClient(model, baseURL) {
|
|
|
452
655
|
}
|
|
453
656
|
throw error;
|
|
454
657
|
}
|
|
455
|
-
onChunk({ type: "done" });
|
|
456
|
-
return {
|
|
457
|
-
message: { role: "assistant", content: fullText },
|
|
458
|
-
toolUses: []
|
|
459
|
-
};
|
|
460
658
|
}
|
|
461
659
|
};
|
|
462
660
|
}
|
|
@@ -599,20 +797,20 @@ var McpManager = class {
|
|
|
599
797
|
|
|
600
798
|
// src/agent.ts
|
|
601
799
|
import * as readline from "readline";
|
|
602
|
-
import
|
|
603
|
-
import
|
|
604
|
-
import
|
|
605
|
-
import
|
|
800
|
+
import fs12 from "fs";
|
|
801
|
+
import path12 from "path";
|
|
802
|
+
import os11 from "os";
|
|
803
|
+
import pc6 from "picocolors";
|
|
606
804
|
import { marked } from "marked";
|
|
607
805
|
import { markedTerminal } from "marked-terminal";
|
|
608
806
|
import logUpdate from "log-update";
|
|
609
807
|
|
|
610
808
|
// src/commands.ts
|
|
611
|
-
import
|
|
612
|
-
import
|
|
613
|
-
import
|
|
809
|
+
import fs8 from "fs";
|
|
810
|
+
import path8 from "path";
|
|
811
|
+
import os8 from "os";
|
|
614
812
|
import { execFileSync } from "child_process";
|
|
615
|
-
import
|
|
813
|
+
import pc3 from "picocolors";
|
|
616
814
|
|
|
617
815
|
// src/layers/parsers.ts
|
|
618
816
|
import fs4 from "fs";
|
|
@@ -671,12 +869,721 @@ function getEcosystemStatus(mcpToolCount, amemConnected) {
|
|
|
671
869
|
};
|
|
672
870
|
}
|
|
673
871
|
|
|
872
|
+
// src/profile-templates.ts
|
|
873
|
+
import fs5 from "fs";
|
|
874
|
+
import path5 from "path";
|
|
875
|
+
import os5 from "os";
|
|
876
|
+
var BUILT_IN_PROFILES = [
|
|
877
|
+
{
|
|
878
|
+
name: "coder",
|
|
879
|
+
label: "Coder",
|
|
880
|
+
description: "Direct, technical, code-first. Skips pleasantries, shows code.",
|
|
881
|
+
core: `# Coder
|
|
882
|
+
|
|
883
|
+
## Identity
|
|
884
|
+
- Role: Coder is your technical pair programmer
|
|
885
|
+
- Personality: direct, precise, efficient \u2014 code speaks louder than words
|
|
886
|
+
- Communication: lead with code, explain after. No fluff.
|
|
887
|
+
- Values: simplicity over cleverness, working code over perfect code, tests over trust
|
|
888
|
+
- Boundaries: won't pretend to be human, flags when out of depth
|
|
889
|
+
|
|
890
|
+
### Appearance
|
|
891
|
+
- Base: focused developer, dark hoodie, terminal glow
|
|
892
|
+
- Style: minimal
|
|
893
|
+
- Palette: green on black`,
|
|
894
|
+
rules: `# Coder Rules
|
|
895
|
+
|
|
896
|
+
## Always
|
|
897
|
+
- Show code before explaining
|
|
898
|
+
- Include error handling
|
|
899
|
+
- Suggest tests for new code
|
|
900
|
+
|
|
901
|
+
## Never
|
|
902
|
+
- Write code without understanding the requirement
|
|
903
|
+
- Push to main without tests
|
|
904
|
+
- Ignore security implications`
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "writer",
|
|
908
|
+
label: "Writer",
|
|
909
|
+
description: "Creative, eloquent, story-driven. Focuses on narrative and engagement.",
|
|
910
|
+
core: `# Muse
|
|
911
|
+
|
|
912
|
+
## Identity
|
|
913
|
+
- Role: Muse is your creative writing partner
|
|
914
|
+
- Personality: eloquent, imaginative, encouraging \u2014 finds the story in everything
|
|
915
|
+
- Communication: explore ideas together, offer alternatives, celebrate good writing
|
|
916
|
+
- Values: authenticity over formulas, voice over grammar, emotion over information
|
|
917
|
+
- Boundaries: won't write without understanding the audience, flags when content is sensitive
|
|
918
|
+
|
|
919
|
+
### Appearance
|
|
920
|
+
- Base: warm expression, creative energy, pen in hand
|
|
921
|
+
- Style: illustrated
|
|
922
|
+
- Palette: warm amber and cream`,
|
|
923
|
+
rules: `# Writer Rules
|
|
924
|
+
|
|
925
|
+
## Always
|
|
926
|
+
- Ask about the target audience
|
|
927
|
+
- Offer 2-3 angle options before drafting
|
|
928
|
+
- Read drafts aloud mentally for rhythm
|
|
929
|
+
|
|
930
|
+
## Never
|
|
931
|
+
- Use cliches without subverting them
|
|
932
|
+
- Write without a clear hook
|
|
933
|
+
- Ignore tone consistency`
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "researcher",
|
|
937
|
+
label: "Researcher",
|
|
938
|
+
description: "Analytical, thorough, citation-focused. Digs deep, verifies claims.",
|
|
939
|
+
core: `# Scholar
|
|
940
|
+
|
|
941
|
+
## Identity
|
|
942
|
+
- Role: Scholar is your research analyst
|
|
943
|
+
- Personality: analytical, thorough, intellectually curious \u2014 never takes claims at face value
|
|
944
|
+
- Communication: present findings with evidence, flag uncertainty, compare perspectives
|
|
945
|
+
- Values: accuracy over speed, nuance over simplification, primary sources over summaries
|
|
946
|
+
- Boundaries: clearly marks speculation vs fact, flags when evidence is insufficient
|
|
947
|
+
|
|
948
|
+
### Appearance
|
|
949
|
+
- Base: thoughtful expression, glasses, surrounded by notes
|
|
950
|
+
- Style: minimal
|
|
951
|
+
- Palette: navy and white`,
|
|
952
|
+
rules: `# Researcher Rules
|
|
953
|
+
|
|
954
|
+
## Always
|
|
955
|
+
- Cite sources when making factual claims
|
|
956
|
+
- Flag confidence level (high/medium/low)
|
|
957
|
+
- Present multiple perspectives on contested topics
|
|
958
|
+
|
|
959
|
+
## Never
|
|
960
|
+
- Present speculation as fact
|
|
961
|
+
- Ignore contradicting evidence
|
|
962
|
+
- Oversimplify complex topics`
|
|
963
|
+
}
|
|
964
|
+
];
|
|
965
|
+
function installProfileTemplate(templateName, userName) {
|
|
966
|
+
const template = BUILT_IN_PROFILES.find((t) => t.name === templateName);
|
|
967
|
+
if (!template) return null;
|
|
968
|
+
const profileDir = path5.join(os5.homedir(), ".acore", "profiles", template.name);
|
|
969
|
+
if (fs5.existsSync(profileDir)) return `Profile already exists: ${template.name}`;
|
|
970
|
+
fs5.mkdirSync(profileDir, { recursive: true });
|
|
971
|
+
let core = template.core;
|
|
972
|
+
if (userName) {
|
|
973
|
+
core += `
|
|
974
|
+
|
|
975
|
+
---
|
|
976
|
+
|
|
977
|
+
## Relationship
|
|
978
|
+
- Name: ${userName}
|
|
979
|
+
- Nicknames: []
|
|
980
|
+
- Communication: [updated over time]
|
|
981
|
+
- Detail level: balanced
|
|
982
|
+
`;
|
|
983
|
+
}
|
|
984
|
+
fs5.writeFileSync(path5.join(profileDir, "core.md"), core, "utf-8");
|
|
985
|
+
if (template.rules) {
|
|
986
|
+
fs5.writeFileSync(path5.join(profileDir, "rules.md"), template.rules, "utf-8");
|
|
987
|
+
}
|
|
988
|
+
if (template.skills) {
|
|
989
|
+
fs5.writeFileSync(path5.join(profileDir, "skills.md"), template.skills, "utf-8");
|
|
990
|
+
}
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/delegate.ts
|
|
995
|
+
import pc from "picocolors";
|
|
996
|
+
var isRetryable = (err) => {
|
|
997
|
+
if (err instanceof Error) {
|
|
998
|
+
const msg = err.message.toLowerCase();
|
|
999
|
+
return msg.includes("rate") || msg.includes("timeout") || msg.includes("econnreset");
|
|
1000
|
+
}
|
|
1001
|
+
return false;
|
|
1002
|
+
};
|
|
1003
|
+
async function delegateTask(task, profile, client, mcpManager, options = {}) {
|
|
1004
|
+
const maxTurns = options.maxTurns ?? 10;
|
|
1005
|
+
const silent = options.silent ?? false;
|
|
1006
|
+
const tools = options.tools;
|
|
1007
|
+
try {
|
|
1008
|
+
const { prompt: systemPrompt } = assembleSystemPrompt(void 0, profile);
|
|
1009
|
+
const delegationPrompt = `${systemPrompt}
|
|
1010
|
+
|
|
1011
|
+
<delegation>
|
|
1012
|
+
You are being delegated a specific task by the primary agent. Complete this task thoroughly and return your result. You have access to tools if needed. Focus on the task \u2014 do not ask follow-up questions, just do your best with what you have.
|
|
1013
|
+
</delegation>`;
|
|
1014
|
+
const messages = [
|
|
1015
|
+
{ role: "user", content: task }
|
|
1016
|
+
];
|
|
1017
|
+
const toolsUsed = [];
|
|
1018
|
+
let turns = 0;
|
|
1019
|
+
const onChunk = silent ? () => {
|
|
1020
|
+
} : (chunk) => {
|
|
1021
|
+
if (chunk.type === "text" && chunk.text) {
|
|
1022
|
+
process.stdout.write(chunk.text);
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
let response = await withRetry(
|
|
1026
|
+
() => client.chat(delegationPrompt, messages, onChunk, tools),
|
|
1027
|
+
{ maxAttempts: 2, baseDelay: 1e3, retryable: isRetryable }
|
|
1028
|
+
);
|
|
1029
|
+
messages.push(response.message);
|
|
1030
|
+
while (response.toolUses.length > 0 && turns < maxTurns) {
|
|
1031
|
+
turns++;
|
|
1032
|
+
const toolResults = await Promise.all(
|
|
1033
|
+
response.toolUses.map(async (toolUse) => {
|
|
1034
|
+
if (!silent) {
|
|
1035
|
+
process.stdout.write(pc.dim(` [${profile}:${toolUse.name}...]
|
|
1036
|
+
`));
|
|
1037
|
+
}
|
|
1038
|
+
toolsUsed.push(toolUse.name);
|
|
1039
|
+
try {
|
|
1040
|
+
const result = await mcpManager.callTool(toolUse.name, toolUse.input);
|
|
1041
|
+
return {
|
|
1042
|
+
type: "tool_result",
|
|
1043
|
+
tool_use_id: toolUse.id,
|
|
1044
|
+
content: result
|
|
1045
|
+
};
|
|
1046
|
+
} catch (err) {
|
|
1047
|
+
return {
|
|
1048
|
+
type: "tool_result",
|
|
1049
|
+
tool_use_id: toolUse.id,
|
|
1050
|
+
content: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1051
|
+
is_error: true
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
messages.push({ role: "user", content: toolResults });
|
|
1057
|
+
response = await withRetry(
|
|
1058
|
+
() => client.chat(delegationPrompt, messages, onChunk, tools),
|
|
1059
|
+
{ maxAttempts: 2, baseDelay: 1e3, retryable: isRetryable }
|
|
1060
|
+
);
|
|
1061
|
+
messages.push(response.message);
|
|
1062
|
+
}
|
|
1063
|
+
const finalMessage = response.message;
|
|
1064
|
+
const responseText = typeof finalMessage.content === "string" ? finalMessage.content : finalMessage.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
1065
|
+
return {
|
|
1066
|
+
profile,
|
|
1067
|
+
task,
|
|
1068
|
+
response: responseText,
|
|
1069
|
+
toolsUsed: [...new Set(toolsUsed)],
|
|
1070
|
+
turns,
|
|
1071
|
+
success: true
|
|
1072
|
+
};
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
1075
|
+
log.warn("delegate", `Delegation to ${profile} failed: ${error}`);
|
|
1076
|
+
return {
|
|
1077
|
+
profile,
|
|
1078
|
+
task,
|
|
1079
|
+
response: "",
|
|
1080
|
+
toolsUsed: [],
|
|
1081
|
+
turns: 0,
|
|
1082
|
+
success: false,
|
|
1083
|
+
error
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
async function delegateParallel(tasks, client, mcpManager, options = {}) {
|
|
1088
|
+
return Promise.all(
|
|
1089
|
+
tasks.map(
|
|
1090
|
+
({ task, profile }) => delegateTask(task, profile, client, mcpManager, { ...options, silent: true })
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
async function delegatePipeline(steps, initialInput, client, mcpManager, options = {}) {
|
|
1095
|
+
const results = [];
|
|
1096
|
+
let previousResult = initialInput;
|
|
1097
|
+
for (const step of steps) {
|
|
1098
|
+
const task = step.taskTemplate.replace("{{input}}", previousResult);
|
|
1099
|
+
if (!options.silent) {
|
|
1100
|
+
process.stdout.write(pc.dim(`
|
|
1101
|
+
[delegating to ${step.profile}...]
|
|
1102
|
+
`));
|
|
1103
|
+
}
|
|
1104
|
+
const result = await delegateTask(task, step.profile, client, mcpManager, {
|
|
1105
|
+
...options,
|
|
1106
|
+
silent: true
|
|
1107
|
+
});
|
|
1108
|
+
results.push(result);
|
|
1109
|
+
if (!result.success) break;
|
|
1110
|
+
previousResult = result.response;
|
|
1111
|
+
}
|
|
1112
|
+
return results;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// src/teams.ts
|
|
1116
|
+
import fs6 from "fs";
|
|
1117
|
+
import path6 from "path";
|
|
1118
|
+
import os6 from "os";
|
|
1119
|
+
import pc2 from "picocolors";
|
|
1120
|
+
function getTeamsDir() {
|
|
1121
|
+
return path6.join(os6.homedir(), ".acore", "teams");
|
|
1122
|
+
}
|
|
1123
|
+
function ensureTeamsDir() {
|
|
1124
|
+
const dir = getTeamsDir();
|
|
1125
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
1126
|
+
return dir;
|
|
1127
|
+
}
|
|
1128
|
+
function teamPath(name) {
|
|
1129
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
1130
|
+
return path6.join(ensureTeamsDir(), `${slug}.json`);
|
|
1131
|
+
}
|
|
1132
|
+
function createTeam(team) {
|
|
1133
|
+
const fp = teamPath(team.name);
|
|
1134
|
+
fs6.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
|
|
1135
|
+
}
|
|
1136
|
+
function loadTeam(name) {
|
|
1137
|
+
const fp = teamPath(name);
|
|
1138
|
+
if (!fs6.existsSync(fp)) return null;
|
|
1139
|
+
try {
|
|
1140
|
+
return JSON.parse(fs6.readFileSync(fp, "utf-8"));
|
|
1141
|
+
} catch {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function listTeams() {
|
|
1146
|
+
const dir = getTeamsDir();
|
|
1147
|
+
if (!fs6.existsSync(dir)) return [];
|
|
1148
|
+
const teams = [];
|
|
1149
|
+
for (const file of fs6.readdirSync(dir)) {
|
|
1150
|
+
if (!file.endsWith(".json")) continue;
|
|
1151
|
+
try {
|
|
1152
|
+
const content = fs6.readFileSync(path6.join(dir, file), "utf-8");
|
|
1153
|
+
teams.push(JSON.parse(content));
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return teams;
|
|
1158
|
+
}
|
|
1159
|
+
function deleteTeam(name) {
|
|
1160
|
+
const fp = teamPath(name);
|
|
1161
|
+
if (!fs6.existsSync(fp)) return false;
|
|
1162
|
+
fs6.unlinkSync(fp);
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
async function runTeam(team, task, client, mcpManager, tools) {
|
|
1166
|
+
process.stdout.write(pc2.dim(`
|
|
1167
|
+
Team: ${team.name} (${team.workflow} mode)
|
|
1168
|
+
`));
|
|
1169
|
+
process.stdout.write(pc2.dim(` Members: ${team.members.map((m) => m.profile).join(", ")}
|
|
1170
|
+
|
|
1171
|
+
`));
|
|
1172
|
+
switch (team.workflow) {
|
|
1173
|
+
case "pipeline":
|
|
1174
|
+
return runPipeline(team, task, client, mcpManager, tools);
|
|
1175
|
+
case "parallel":
|
|
1176
|
+
return runParallel(team, task, client, mcpManager, tools);
|
|
1177
|
+
case "coordinator":
|
|
1178
|
+
return runCoordinator(team, task, client, mcpManager, tools);
|
|
1179
|
+
default:
|
|
1180
|
+
return {
|
|
1181
|
+
team: team.name,
|
|
1182
|
+
task,
|
|
1183
|
+
workflow: team.workflow,
|
|
1184
|
+
results: [],
|
|
1185
|
+
finalOutput: `Unknown workflow mode: ${team.workflow}`,
|
|
1186
|
+
success: false
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async function runPipeline(team, task, client, mcpManager, tools) {
|
|
1191
|
+
const steps = team.members.map((m, i) => ({
|
|
1192
|
+
profile: m.profile,
|
|
1193
|
+
taskTemplate: i === 0 ? `${task}
|
|
1194
|
+
|
|
1195
|
+
Your role: ${m.role}` : `${m.role}. Here is the previous agent's work:
|
|
1196
|
+
|
|
1197
|
+
{{input}}`
|
|
1198
|
+
}));
|
|
1199
|
+
for (const step of steps) {
|
|
1200
|
+
process.stdout.write(pc2.dim(` [${step.profile}: ${team.members.find((m) => m.profile === step.profile)?.role}...]
|
|
1201
|
+
`));
|
|
1202
|
+
}
|
|
1203
|
+
const results = await delegatePipeline(steps, task, client, mcpManager, {
|
|
1204
|
+
tools,
|
|
1205
|
+
silent: true
|
|
1206
|
+
});
|
|
1207
|
+
const lastResult = results[results.length - 1];
|
|
1208
|
+
const success = results.every((r) => r.success);
|
|
1209
|
+
return {
|
|
1210
|
+
team: team.name,
|
|
1211
|
+
task,
|
|
1212
|
+
workflow: "pipeline",
|
|
1213
|
+
results,
|
|
1214
|
+
finalOutput: lastResult?.response || "",
|
|
1215
|
+
success
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async function runParallel(team, task, client, mcpManager, tools) {
|
|
1219
|
+
const tasks = team.members.map((m) => ({
|
|
1220
|
+
profile: m.profile,
|
|
1221
|
+
task: `${task}
|
|
1222
|
+
|
|
1223
|
+
Your specific role: ${m.role}. Focus only on your role.`
|
|
1224
|
+
}));
|
|
1225
|
+
for (const m of team.members) {
|
|
1226
|
+
process.stdout.write(pc2.dim(` [${m.profile}: ${m.role} (parallel)...]
|
|
1227
|
+
`));
|
|
1228
|
+
}
|
|
1229
|
+
const results = await delegateParallel(tasks, client, mcpManager, { tools });
|
|
1230
|
+
const mergeInput = results.filter((r) => r.success).map((r) => `[${r.profile} \u2014 ${team.members.find((m) => m.profile === r.profile)?.role}]:
|
|
1231
|
+
${r.response}`).join("\n\n---\n\n");
|
|
1232
|
+
process.stdout.write(pc2.dim(` [merging results...]
|
|
1233
|
+
`));
|
|
1234
|
+
const mergeResult = await delegateTask(
|
|
1235
|
+
`You are the team coordinator. Multiple agents worked on this task in parallel. Merge their outputs into a single cohesive result. Keep the best parts from each.
|
|
1236
|
+
|
|
1237
|
+
Original task: ${task}
|
|
1238
|
+
|
|
1239
|
+
${mergeInput}`,
|
|
1240
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1241
|
+
client,
|
|
1242
|
+
mcpManager,
|
|
1243
|
+
{ tools, silent: true }
|
|
1244
|
+
);
|
|
1245
|
+
return {
|
|
1246
|
+
team: team.name,
|
|
1247
|
+
task,
|
|
1248
|
+
workflow: "parallel",
|
|
1249
|
+
results: [...results, mergeResult],
|
|
1250
|
+
finalOutput: mergeResult.response,
|
|
1251
|
+
success: results.some((r) => r.success)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
async function runCoordinator(team, task, client, mcpManager, tools) {
|
|
1255
|
+
const memberDescriptions = team.members.map((m) => `- ${m.profile}: ${m.role}`).join("\n");
|
|
1256
|
+
process.stdout.write(pc2.dim(` [coordinator planning...]
|
|
1257
|
+
`));
|
|
1258
|
+
const planResult = await delegateTask(
|
|
1259
|
+
`You are the coordinator of a team. Your job is to break down this task and decide which team members should handle each part.
|
|
1260
|
+
|
|
1261
|
+
Team members:
|
|
1262
|
+
${memberDescriptions}
|
|
1263
|
+
|
|
1264
|
+
Task: ${task}
|
|
1265
|
+
|
|
1266
|
+
Respond with a JSON array of assignments:
|
|
1267
|
+
[{"profile": "member-name", "subtask": "what they should do"}]
|
|
1268
|
+
|
|
1269
|
+
Only use the JSON array, no other text.`,
|
|
1270
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1271
|
+
client,
|
|
1272
|
+
mcpManager,
|
|
1273
|
+
{ tools: void 0, silent: true, maxTurns: 0 }
|
|
1274
|
+
);
|
|
1275
|
+
if (!planResult.success) {
|
|
1276
|
+
return {
|
|
1277
|
+
team: team.name,
|
|
1278
|
+
task,
|
|
1279
|
+
workflow: "coordinator",
|
|
1280
|
+
results: [planResult],
|
|
1281
|
+
finalOutput: `Coordinator failed to plan: ${planResult.error}`,
|
|
1282
|
+
success: false
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
let assignments;
|
|
1286
|
+
try {
|
|
1287
|
+
let cleaned = planResult.response.trim();
|
|
1288
|
+
const codeBlockMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
1289
|
+
if (codeBlockMatch) cleaned = codeBlockMatch[1].trim();
|
|
1290
|
+
assignments = JSON.parse(cleaned);
|
|
1291
|
+
} catch {
|
|
1292
|
+
assignments = team.members.map((m) => ({ profile: m.profile, subtask: `${m.role}: ${task}` }));
|
|
1293
|
+
}
|
|
1294
|
+
for (const a of assignments) {
|
|
1295
|
+
process.stdout.write(pc2.dim(` [${a.profile}: ${a.subtask.slice(0, 60)}...]
|
|
1296
|
+
`));
|
|
1297
|
+
}
|
|
1298
|
+
const results = await delegateParallel(
|
|
1299
|
+
assignments.map((a) => ({ profile: a.profile, task: a.subtask })),
|
|
1300
|
+
client,
|
|
1301
|
+
mcpManager,
|
|
1302
|
+
{ tools }
|
|
1303
|
+
);
|
|
1304
|
+
const mergeInput = results.filter((r) => r.success).map((r, i) => `[${assignments[i]?.profile} \u2014 ${assignments[i]?.subtask}]:
|
|
1305
|
+
${r.response}`).join("\n\n---\n\n");
|
|
1306
|
+
process.stdout.write(pc2.dim(` [coordinator merging...]
|
|
1307
|
+
`));
|
|
1308
|
+
const mergeResult = await delegateTask(
|
|
1309
|
+
`You are the team coordinator. Your team members completed their assigned work. Combine their outputs into a single cohesive, polished result.
|
|
1310
|
+
|
|
1311
|
+
Original task: ${task}
|
|
1312
|
+
|
|
1313
|
+
${mergeInput}`,
|
|
1314
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1315
|
+
client,
|
|
1316
|
+
mcpManager,
|
|
1317
|
+
{ tools, silent: true }
|
|
1318
|
+
);
|
|
1319
|
+
return {
|
|
1320
|
+
team: team.name,
|
|
1321
|
+
task,
|
|
1322
|
+
workflow: "coordinator",
|
|
1323
|
+
results: [...results, mergeResult],
|
|
1324
|
+
finalOutput: mergeResult.response,
|
|
1325
|
+
success: results.some((r) => r.success)
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function formatTeam(team) {
|
|
1329
|
+
const lines = [];
|
|
1330
|
+
lines.push(`Team: ${pc2.bold(team.name)}`);
|
|
1331
|
+
lines.push(`Goal: ${team.goal}`);
|
|
1332
|
+
lines.push(`Mode: ${team.workflow}`);
|
|
1333
|
+
lines.push(`Coordinator: ${team.coordinator}`);
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
lines.push("Members:");
|
|
1336
|
+
for (const m of team.members) {
|
|
1337
|
+
lines.push(` ${pc2.bold(m.profile)} \u2014 ${m.role}`);
|
|
1338
|
+
}
|
|
1339
|
+
return lines.join("\n");
|
|
1340
|
+
}
|
|
1341
|
+
function formatTeamResult(result) {
|
|
1342
|
+
const lines = [];
|
|
1343
|
+
lines.push(`
|
|
1344
|
+
${pc2.bold(`Team: ${result.team}`)} (${result.workflow})`);
|
|
1345
|
+
for (const r of result.results) {
|
|
1346
|
+
const status = r.success ? pc2.green("\u2713") : pc2.red("\u2717");
|
|
1347
|
+
const tools = r.toolsUsed.length > 0 ? pc2.dim(` (${r.toolsUsed.join(", ")})`) : "";
|
|
1348
|
+
lines.push(` ${status} ${pc2.bold(r.profile)}${tools}`);
|
|
1349
|
+
}
|
|
1350
|
+
lines.push("");
|
|
1351
|
+
lines.push(result.finalOutput);
|
|
1352
|
+
return lines.join("\n");
|
|
1353
|
+
}
|
|
1354
|
+
var BUILT_IN_TEAMS = [
|
|
1355
|
+
{
|
|
1356
|
+
name: "content-team",
|
|
1357
|
+
goal: "Create and publish high-quality content",
|
|
1358
|
+
coordinator: "default",
|
|
1359
|
+
members: [
|
|
1360
|
+
{ profile: "writer", role: "Draft compelling content with engaging narrative" },
|
|
1361
|
+
{ profile: "researcher", role: "Fact-check claims and add citations" }
|
|
1362
|
+
],
|
|
1363
|
+
workflow: "pipeline"
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
name: "dev-team",
|
|
1367
|
+
goal: "Build and review code with quality assurance",
|
|
1368
|
+
coordinator: "default",
|
|
1369
|
+
members: [
|
|
1370
|
+
{ profile: "coder", role: "Write clean, tested implementation code" },
|
|
1371
|
+
{ profile: "researcher", role: "Review for security, performance, and best practices" }
|
|
1372
|
+
],
|
|
1373
|
+
workflow: "pipeline"
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
name: "research-team",
|
|
1377
|
+
goal: "Deep research with multiple perspectives",
|
|
1378
|
+
coordinator: "default",
|
|
1379
|
+
members: [
|
|
1380
|
+
{ profile: "researcher", role: "Research the topic thoroughly with citations" },
|
|
1381
|
+
{ profile: "writer", role: "Synthesize findings into clear, readable format" }
|
|
1382
|
+
],
|
|
1383
|
+
workflow: "pipeline"
|
|
1384
|
+
}
|
|
1385
|
+
];
|
|
1386
|
+
|
|
1387
|
+
// src/plans.ts
|
|
1388
|
+
import fs7 from "fs";
|
|
1389
|
+
import path7 from "path";
|
|
1390
|
+
import os7 from "os";
|
|
1391
|
+
function getPlansDir() {
|
|
1392
|
+
const localDir = path7.join(process.cwd(), ".acore", "plans");
|
|
1393
|
+
const localAcore = path7.join(process.cwd(), ".acore");
|
|
1394
|
+
if (fs7.existsSync(localAcore)) return localDir;
|
|
1395
|
+
return path7.join(os7.homedir(), ".acore", "plans");
|
|
1396
|
+
}
|
|
1397
|
+
function ensurePlansDir() {
|
|
1398
|
+
const dir = getPlansDir();
|
|
1399
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
1400
|
+
return dir;
|
|
1401
|
+
}
|
|
1402
|
+
function planPath(name) {
|
|
1403
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1404
|
+
return path7.join(ensurePlansDir(), `${slug}.md`);
|
|
1405
|
+
}
|
|
1406
|
+
function serializePlan(plan) {
|
|
1407
|
+
const lines = [];
|
|
1408
|
+
lines.push(`# ${plan.name}`);
|
|
1409
|
+
lines.push("");
|
|
1410
|
+
lines.push(`**Goal:** ${plan.goal}`);
|
|
1411
|
+
lines.push(`**Created:** ${plan.createdAt}`);
|
|
1412
|
+
lines.push(`**Updated:** ${plan.updatedAt}`);
|
|
1413
|
+
lines.push(`**Active:** ${plan.active}`);
|
|
1414
|
+
lines.push("");
|
|
1415
|
+
lines.push("## Steps");
|
|
1416
|
+
lines.push("");
|
|
1417
|
+
for (const step of plan.steps) {
|
|
1418
|
+
lines.push(`- [${step.done ? "x" : " "}] ${step.text}`);
|
|
1419
|
+
}
|
|
1420
|
+
lines.push("");
|
|
1421
|
+
return lines.join("\n");
|
|
1422
|
+
}
|
|
1423
|
+
function parsePlan(content, filePath) {
|
|
1424
|
+
try {
|
|
1425
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
1426
|
+
const goalMatch = content.match(/\*\*Goal:\*\*\s*(.+)/);
|
|
1427
|
+
const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
|
|
1428
|
+
const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
|
|
1429
|
+
const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
|
|
1430
|
+
const name = nameMatch?.[1]?.trim() || path7.basename(filePath, ".md");
|
|
1431
|
+
const goal = goalMatch?.[1]?.trim() || "";
|
|
1432
|
+
const createdAt = createdMatch?.[1]?.trim() || "";
|
|
1433
|
+
const updatedAt = updatedMatch?.[1]?.trim() || "";
|
|
1434
|
+
const active = activeMatch?.[1]?.trim() === "true";
|
|
1435
|
+
const steps = [];
|
|
1436
|
+
const stepMatches = content.matchAll(/- \[([ x])\] (.+)/g);
|
|
1437
|
+
for (const match of stepMatches) {
|
|
1438
|
+
steps.push({
|
|
1439
|
+
done: match[1] === "x",
|
|
1440
|
+
text: match[2].trim()
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
return { name, goal, steps, createdAt, updatedAt, active };
|
|
1444
|
+
} catch (err) {
|
|
1445
|
+
log.debug("plans", "Failed to parse plan: " + filePath, err);
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
function createPlan(name, goal, steps) {
|
|
1450
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1451
|
+
const plan = {
|
|
1452
|
+
name,
|
|
1453
|
+
goal,
|
|
1454
|
+
steps: steps.map((text2) => ({ text: text2, done: false })),
|
|
1455
|
+
createdAt: now,
|
|
1456
|
+
updatedAt: now,
|
|
1457
|
+
active: true
|
|
1458
|
+
};
|
|
1459
|
+
const existing = listPlans();
|
|
1460
|
+
for (const p3 of existing) {
|
|
1461
|
+
if (p3.active) {
|
|
1462
|
+
p3.active = false;
|
|
1463
|
+
p3.updatedAt = now;
|
|
1464
|
+
savePlan(p3);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
savePlan(plan);
|
|
1468
|
+
return plan;
|
|
1469
|
+
}
|
|
1470
|
+
function savePlan(plan) {
|
|
1471
|
+
const fp = planPath(plan.name);
|
|
1472
|
+
fs7.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
1473
|
+
}
|
|
1474
|
+
function loadPlan(name) {
|
|
1475
|
+
const fp = planPath(name);
|
|
1476
|
+
if (!fs7.existsSync(fp)) return null;
|
|
1477
|
+
const content = fs7.readFileSync(fp, "utf-8");
|
|
1478
|
+
return parsePlan(content, fp);
|
|
1479
|
+
}
|
|
1480
|
+
function listPlans() {
|
|
1481
|
+
const dir = getPlansDir();
|
|
1482
|
+
if (!fs7.existsSync(dir)) return [];
|
|
1483
|
+
const plans = [];
|
|
1484
|
+
for (const file of fs7.readdirSync(dir)) {
|
|
1485
|
+
if (!file.endsWith(".md")) continue;
|
|
1486
|
+
const fp = path7.join(dir, file);
|
|
1487
|
+
const content = fs7.readFileSync(fp, "utf-8");
|
|
1488
|
+
const plan = parsePlan(content, fp);
|
|
1489
|
+
if (plan) plans.push(plan);
|
|
1490
|
+
}
|
|
1491
|
+
return plans;
|
|
1492
|
+
}
|
|
1493
|
+
function getActivePlan() {
|
|
1494
|
+
const plans = listPlans();
|
|
1495
|
+
return plans.find((p3) => p3.active) || null;
|
|
1496
|
+
}
|
|
1497
|
+
function markStepDone(plan, stepIndex) {
|
|
1498
|
+
if (stepIndex < 0 || stepIndex >= plan.steps.length) return false;
|
|
1499
|
+
plan.steps[stepIndex].done = true;
|
|
1500
|
+
plan.updatedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1501
|
+
savePlan(plan);
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
function markStepUndone(plan, stepIndex) {
|
|
1505
|
+
if (stepIndex < 0 || stepIndex >= plan.steps.length) return false;
|
|
1506
|
+
plan.steps[stepIndex].done = false;
|
|
1507
|
+
plan.updatedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1508
|
+
savePlan(plan);
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
function setActivePlan(name) {
|
|
1512
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1513
|
+
const plans = listPlans();
|
|
1514
|
+
for (const p3 of plans) {
|
|
1515
|
+
if (p3.active) {
|
|
1516
|
+
p3.active = false;
|
|
1517
|
+
p3.updatedAt = now;
|
|
1518
|
+
savePlan(p3);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
const target = loadPlan(name);
|
|
1522
|
+
if (!target) return null;
|
|
1523
|
+
target.active = true;
|
|
1524
|
+
target.updatedAt = now;
|
|
1525
|
+
savePlan(target);
|
|
1526
|
+
return target;
|
|
1527
|
+
}
|
|
1528
|
+
function formatPlan(plan) {
|
|
1529
|
+
const total = plan.steps.length;
|
|
1530
|
+
const done = plan.steps.filter((s) => s.done).length;
|
|
1531
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
1532
|
+
const bar = progressBar(pct);
|
|
1533
|
+
const lines = [];
|
|
1534
|
+
lines.push(`Plan: ${plan.name} ${plan.active ? "(active)" : "(inactive)"}`);
|
|
1535
|
+
lines.push(`Goal: ${plan.goal}`);
|
|
1536
|
+
lines.push(`Progress: ${bar} ${done}/${total} (${pct}%)`);
|
|
1537
|
+
lines.push("");
|
|
1538
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
1539
|
+
const step = plan.steps[i];
|
|
1540
|
+
const marker = step.done ? "\u2713" : " ";
|
|
1541
|
+
const num = String(i + 1).padStart(2, " ");
|
|
1542
|
+
lines.push(` ${num}. [${marker}] ${step.text}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (done === total && total > 0) {
|
|
1545
|
+
lines.push("\n All steps complete!");
|
|
1546
|
+
} else {
|
|
1547
|
+
const next = plan.steps.findIndex((s) => !s.done);
|
|
1548
|
+
if (next >= 0) {
|
|
1549
|
+
lines.push(`
|
|
1550
|
+
Next: Step ${next + 1} \u2014 ${plan.steps[next].text}`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return lines.join("\n");
|
|
1554
|
+
}
|
|
1555
|
+
function formatPlanForPrompt(plan) {
|
|
1556
|
+
const total = plan.steps.length;
|
|
1557
|
+
const done = plan.steps.filter((s) => s.done).length;
|
|
1558
|
+
const lines = [];
|
|
1559
|
+
lines.push(`<active-plan name="${plan.name}" progress="${done}/${total}">`);
|
|
1560
|
+
lines.push(`Goal: ${plan.goal}`);
|
|
1561
|
+
lines.push("");
|
|
1562
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
1563
|
+
const step = plan.steps[i];
|
|
1564
|
+
lines.push(`- [${step.done ? "x" : " "}] Step ${i + 1}: ${step.text}`);
|
|
1565
|
+
}
|
|
1566
|
+
const next = plan.steps.findIndex((s) => !s.done);
|
|
1567
|
+
if (next >= 0) {
|
|
1568
|
+
lines.push("");
|
|
1569
|
+
lines.push(`Current focus: Step ${next + 1} \u2014 ${plan.steps[next].text}`);
|
|
1570
|
+
lines.push("After completing the current step, remind the user to mark it done with /plan done and suggest committing their work.");
|
|
1571
|
+
}
|
|
1572
|
+
lines.push("</active-plan>");
|
|
1573
|
+
return lines.join("\n");
|
|
1574
|
+
}
|
|
1575
|
+
function progressBar(pct) {
|
|
1576
|
+
const filled = Math.round(pct / 5);
|
|
1577
|
+
const empty = 20 - filled;
|
|
1578
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}]`;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
674
1581
|
// src/commands.ts
|
|
675
1582
|
function readEcosystemFile(filePath, label) {
|
|
676
|
-
if (!
|
|
677
|
-
return
|
|
1583
|
+
if (!fs8.existsSync(filePath)) {
|
|
1584
|
+
return pc3.dim(`No ${label} file found at ${filePath}`);
|
|
678
1585
|
}
|
|
679
|
-
return
|
|
1586
|
+
return fs8.readFileSync(filePath, "utf-8").trim();
|
|
680
1587
|
}
|
|
681
1588
|
function parseCommand(input) {
|
|
682
1589
|
const trimmed = input.trim();
|
|
@@ -688,25 +1595,25 @@ function parseCommand(input) {
|
|
|
688
1595
|
}
|
|
689
1596
|
async function mcpWrite(ctx, layer, tool, args) {
|
|
690
1597
|
if (!ctx.mcpManager) {
|
|
691
|
-
return
|
|
1598
|
+
return pc3.red(`Cannot modify ${layer}: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp`);
|
|
692
1599
|
}
|
|
693
1600
|
const result = await ctx.mcpManager.callTool(tool, args);
|
|
694
1601
|
if (result.startsWith("Error")) {
|
|
695
|
-
return
|
|
1602
|
+
return pc3.red(result);
|
|
696
1603
|
}
|
|
697
|
-
return
|
|
1604
|
+
return pc3.green(result);
|
|
698
1605
|
}
|
|
699
1606
|
async function handleIdentityCommand(action, args, ctx) {
|
|
700
|
-
const home2 =
|
|
1607
|
+
const home2 = os8.homedir();
|
|
701
1608
|
if (!action) {
|
|
702
|
-
const content = readEcosystemFile(
|
|
1609
|
+
const content = readEcosystemFile(path8.join(home2, ".acore", "core.md"), "identity (acore)");
|
|
703
1610
|
return { handled: true, output: content };
|
|
704
1611
|
}
|
|
705
1612
|
if (action === "update") {
|
|
706
1613
|
if (args.length === 0) {
|
|
707
1614
|
return {
|
|
708
1615
|
handled: true,
|
|
709
|
-
output:
|
|
1616
|
+
output: pc3.yellow("Usage: /identity update <section>\nTip: describe changes in natural language and the AI will update via MCP.")
|
|
710
1617
|
};
|
|
711
1618
|
}
|
|
712
1619
|
const section = args[0];
|
|
@@ -714,23 +1621,23 @@ async function handleIdentityCommand(action, args, ctx) {
|
|
|
714
1621
|
if (!content) {
|
|
715
1622
|
return {
|
|
716
1623
|
handled: true,
|
|
717
|
-
output:
|
|
1624
|
+
output: pc3.yellow("Usage: /identity update <section> <new content...>\nExample: /identity update Personality Warm, curious, and direct.")
|
|
718
1625
|
};
|
|
719
1626
|
}
|
|
720
1627
|
const output = await mcpWrite(ctx, "identity", "identity_update_section", { section, content });
|
|
721
1628
|
return { handled: true, output };
|
|
722
1629
|
}
|
|
723
|
-
return { handled: true, output:
|
|
1630
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /identity ${action}. Use /identity or /identity update <section>.`) };
|
|
724
1631
|
}
|
|
725
1632
|
async function handleRulesCommand(action, args, ctx) {
|
|
726
|
-
const home2 =
|
|
1633
|
+
const home2 = os8.homedir();
|
|
727
1634
|
if (!action) {
|
|
728
|
-
const content = readEcosystemFile(
|
|
1635
|
+
const content = readEcosystemFile(path8.join(home2, ".arules", "rules.md"), "guardrails (arules)");
|
|
729
1636
|
return { handled: true, output: content };
|
|
730
1637
|
}
|
|
731
1638
|
if (action === "add") {
|
|
732
1639
|
if (args.length < 2) {
|
|
733
|
-
return { handled: true, output:
|
|
1640
|
+
return { handled: true, output: pc3.yellow("Usage: /rules add <category> <rule text...>") };
|
|
734
1641
|
}
|
|
735
1642
|
const category = args[0];
|
|
736
1643
|
const rule = args.slice(1).join(" ");
|
|
@@ -739,41 +1646,41 @@ async function handleRulesCommand(action, args, ctx) {
|
|
|
739
1646
|
}
|
|
740
1647
|
if (action === "remove") {
|
|
741
1648
|
if (args.length < 2) {
|
|
742
|
-
return { handled: true, output:
|
|
1649
|
+
return { handled: true, output: pc3.yellow("Usage: /rules remove <category> <index>") };
|
|
743
1650
|
}
|
|
744
1651
|
const output = await mcpWrite(ctx, "rules", "rules_remove", { category: args[0], index: parseInt(args[1], 10) });
|
|
745
1652
|
return { handled: true, output };
|
|
746
1653
|
}
|
|
747
1654
|
if (action === "toggle") {
|
|
748
1655
|
if (args.length < 2) {
|
|
749
|
-
return { handled: true, output:
|
|
1656
|
+
return { handled: true, output: pc3.yellow("Usage: /rules toggle <category> <index>") };
|
|
750
1657
|
}
|
|
751
1658
|
const output = await mcpWrite(ctx, "rules", "rules_toggle", { category: args[0], index: parseInt(args[1], 10) });
|
|
752
1659
|
return { handled: true, output };
|
|
753
1660
|
}
|
|
754
|
-
return { handled: true, output:
|
|
1661
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /rules ${action}. Use /rules [add|remove|toggle].`) };
|
|
755
1662
|
}
|
|
756
1663
|
async function handleWorkflowsCommand(action, args, ctx) {
|
|
757
|
-
const home2 =
|
|
1664
|
+
const home2 = os8.homedir();
|
|
758
1665
|
if (!action) {
|
|
759
|
-
const content = readEcosystemFile(
|
|
1666
|
+
const content = readEcosystemFile(path8.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
760
1667
|
return { handled: true, output: content };
|
|
761
1668
|
}
|
|
762
1669
|
if (action === "add") {
|
|
763
1670
|
if (args.length < 1) {
|
|
764
|
-
return { handled: true, output:
|
|
1671
|
+
return { handled: true, output: pc3.yellow("Usage: /workflows add <name>") };
|
|
765
1672
|
}
|
|
766
1673
|
const output = await mcpWrite(ctx, "workflows", "workflow_add", { name: args.join(" ") });
|
|
767
1674
|
return { handled: true, output };
|
|
768
1675
|
}
|
|
769
1676
|
if (action === "remove") {
|
|
770
1677
|
if (args.length < 1) {
|
|
771
|
-
return { handled: true, output:
|
|
1678
|
+
return { handled: true, output: pc3.yellow("Usage: /workflows remove <name>") };
|
|
772
1679
|
}
|
|
773
1680
|
const output = await mcpWrite(ctx, "workflows", "workflow_remove", { name: args.join(" ") });
|
|
774
1681
|
return { handled: true, output };
|
|
775
1682
|
}
|
|
776
|
-
return { handled: true, output:
|
|
1683
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /workflows ${action}. Use /workflows [add|remove].`) };
|
|
777
1684
|
}
|
|
778
1685
|
var AKIT_REGISTRY = [
|
|
779
1686
|
{ name: "web-search", description: "Search the web for current information", category: "search", mcp: { package: "@anthropic/web-search", command: "npx", args: ["-y", "@anthropic/web-search"] } },
|
|
@@ -790,42 +1697,43 @@ var AKIT_REGISTRY = [
|
|
|
790
1697
|
{ name: "docker", description: "Manage Docker containers", category: "automation", mcp: { package: "@modelcontextprotocol/server-docker", command: "npx", args: ["-y", "@modelcontextprotocol/server-docker"] } },
|
|
791
1698
|
{ name: "slack", description: "Send and read Slack messages", category: "communication", mcp: { package: "@modelcontextprotocol/server-slack", command: "npx", args: ["-y", "@modelcontextprotocol/server-slack"], env: { SLACK_BOT_TOKEN: "" } }, envHint: "Set SLACK_BOT_TOKEN from your Slack app settings" },
|
|
792
1699
|
{ name: "notion", description: "Read and write Notion pages", category: "communication", mcp: { package: "@notionhq/notion-mcp-server", command: "npx", args: ["-y", "@notionhq/notion-mcp-server"], env: { NOTION_API_KEY: "" } }, envHint: "Set NOTION_API_KEY from https://notion.so/my-integrations" },
|
|
1700
|
+
{ name: "social", description: "Post to Bluesky, X/Twitter, Threads", category: "communication", mcp: { package: "@aman_asmuei/aman-social", command: "npx", args: ["-y", "@aman_asmuei/aman-social"] }, envHint: "Set BLUESKY_HANDLE + BLUESKY_APP_PASSWORD, TWITTER_API_KEY + secrets, or THREADS_ACCESS_TOKEN" },
|
|
793
1701
|
{ name: "memory", description: "Persistent AI memory via amem", category: "memory", mcp: { package: "@aman_asmuei/amem", command: "npx", args: ["-y", "@aman_asmuei/amem"] } },
|
|
794
1702
|
{ name: "docling", description: "Convert PDF, DOCX, PPTX, XLSX to markdown", category: "documents", mcp: { package: "docling-mcp", command: "uvx", args: ["docling-mcp"] }, envHint: "Requires Python 3.10+. Install: pip install docling" }
|
|
795
1703
|
];
|
|
796
1704
|
function loadAkitInstalled() {
|
|
797
|
-
const filePath =
|
|
798
|
-
if (!
|
|
1705
|
+
const filePath = path8.join(os8.homedir(), ".akit", "installed.json");
|
|
1706
|
+
if (!fs8.existsSync(filePath)) return [];
|
|
799
1707
|
try {
|
|
800
|
-
return JSON.parse(
|
|
1708
|
+
return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
|
|
801
1709
|
} catch {
|
|
802
1710
|
return [];
|
|
803
1711
|
}
|
|
804
1712
|
}
|
|
805
1713
|
function saveAkitInstalled(tools) {
|
|
806
|
-
const dir =
|
|
807
|
-
|
|
808
|
-
|
|
1714
|
+
const dir = path8.join(os8.homedir(), ".akit");
|
|
1715
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1716
|
+
fs8.writeFileSync(path8.join(dir, "installed.json"), JSON.stringify(tools, null, 2) + "\n", "utf-8");
|
|
809
1717
|
}
|
|
810
1718
|
function addToAmanAgentConfig(name, mcpConfig) {
|
|
811
|
-
const configPath =
|
|
812
|
-
if (!
|
|
1719
|
+
const configPath = path8.join(os8.homedir(), ".aman-agent", "config.json");
|
|
1720
|
+
if (!fs8.existsSync(configPath)) return;
|
|
813
1721
|
try {
|
|
814
|
-
const config = JSON.parse(
|
|
1722
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
815
1723
|
if (!config.mcpServers) config.mcpServers = {};
|
|
816
1724
|
config.mcpServers[name] = mcpConfig;
|
|
817
|
-
|
|
1725
|
+
fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
818
1726
|
} catch {
|
|
819
1727
|
}
|
|
820
1728
|
}
|
|
821
1729
|
function removeFromAmanAgentConfig(name) {
|
|
822
|
-
const configPath =
|
|
823
|
-
if (!
|
|
1730
|
+
const configPath = path8.join(os8.homedir(), ".aman-agent", "config.json");
|
|
1731
|
+
if (!fs8.existsSync(configPath)) return;
|
|
824
1732
|
try {
|
|
825
|
-
const config = JSON.parse(
|
|
1733
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
826
1734
|
if (config.mcpServers) {
|
|
827
1735
|
delete config.mcpServers[name];
|
|
828
|
-
|
|
1736
|
+
fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
829
1737
|
}
|
|
830
1738
|
} catch {
|
|
831
1739
|
}
|
|
@@ -837,27 +1745,27 @@ function handleAkitCommand(action, args) {
|
|
|
837
1745
|
const available2 = AKIT_REGISTRY.filter((t) => !installedNames.has(t.name));
|
|
838
1746
|
if (args.length < 1) {
|
|
839
1747
|
if (available2.length === 0) {
|
|
840
|
-
return { handled: true, output:
|
|
1748
|
+
return { handled: true, output: pc3.green("All tools are installed!") };
|
|
841
1749
|
}
|
|
842
|
-
const lines3 = [
|
|
1750
|
+
const lines3 = [pc3.bold("Select a tool to install:"), ""];
|
|
843
1751
|
available2.forEach((tool2, i) => {
|
|
844
|
-
const num2 =
|
|
845
|
-
lines3.push(` ${num2} ${tool2.name.padEnd(16)} ${
|
|
1752
|
+
const num2 = pc3.cyan(String(i + 1).padStart(2));
|
|
1753
|
+
lines3.push(` ${num2} ${tool2.name.padEnd(16)} ${pc3.dim(tool2.description)}`);
|
|
846
1754
|
});
|
|
847
1755
|
lines3.push("");
|
|
848
|
-
lines3.push(` Type: ${
|
|
849
|
-
lines3.push(` Custom: ${
|
|
1756
|
+
lines3.push(` Type: ${pc3.cyan("/akit add <number>")} or ${pc3.cyan("/akit add <name>")}`);
|
|
1757
|
+
lines3.push(` Custom: ${pc3.cyan("/akit add custom <name> <command> <args...>")}`);
|
|
850
1758
|
return { handled: true, output: lines3.join("\n") };
|
|
851
1759
|
}
|
|
852
1760
|
if (args[0].toLowerCase() === "custom") {
|
|
853
1761
|
if (args.length < 3) {
|
|
854
|
-
return { handled: true, output:
|
|
1762
|
+
return { handled: true, output: pc3.yellow("Usage: /akit add custom <name> <command> <args...>\nExample: /akit add custom my-tool npx -y @org/my-mcp-server") };
|
|
855
1763
|
}
|
|
856
1764
|
const customName = args[1];
|
|
857
1765
|
const customCommand = args[2];
|
|
858
1766
|
const customArgs = args.slice(3);
|
|
859
1767
|
if (installedNames.has(customName)) {
|
|
860
|
-
return { handled: true, output:
|
|
1768
|
+
return { handled: true, output: pc3.yellow(`${customName} is already installed.`) };
|
|
861
1769
|
}
|
|
862
1770
|
installed.push({
|
|
863
1771
|
name: customName,
|
|
@@ -869,8 +1777,8 @@ function handleAkitCommand(action, args) {
|
|
|
869
1777
|
return {
|
|
870
1778
|
handled: true,
|
|
871
1779
|
output: [
|
|
872
|
-
|
|
873
|
-
|
|
1780
|
+
pc3.green(`\u2713 Added ${pc3.bold(customName)}`) + pc3.dim(` (custom MCP: ${customCommand} ${customArgs.join(" ")})`),
|
|
1781
|
+
pc3.dim(" Restart aman-agent to load the new tool.")
|
|
874
1782
|
].join("\n")
|
|
875
1783
|
};
|
|
876
1784
|
}
|
|
@@ -886,13 +1794,13 @@ function handleAkitCommand(action, args) {
|
|
|
886
1794
|
return {
|
|
887
1795
|
handled: true,
|
|
888
1796
|
output: [
|
|
889
|
-
|
|
890
|
-
`Type ${
|
|
1797
|
+
pc3.red(`Tool "${input}" not found.`),
|
|
1798
|
+
`Type ${pc3.cyan("/akit add")} to see available tools.`
|
|
891
1799
|
].join("\n")
|
|
892
1800
|
};
|
|
893
1801
|
}
|
|
894
1802
|
if (installedNames.has(tool.name)) {
|
|
895
|
-
return { handled: true, output:
|
|
1803
|
+
return { handled: true, output: pc3.yellow(`${tool.name} is already installed.`) };
|
|
896
1804
|
}
|
|
897
1805
|
installed.push({
|
|
898
1806
|
name: tool.name,
|
|
@@ -907,139 +1815,139 @@ function handleAkitCommand(action, args) {
|
|
|
907
1815
|
});
|
|
908
1816
|
}
|
|
909
1817
|
const lines2 = [
|
|
910
|
-
|
|
1818
|
+
pc3.green(`\u2713 Added ${pc3.bold(tool.name)}`) + (tool.mcp ? pc3.dim(` (MCP: ${tool.mcp.package})`) : "")
|
|
911
1819
|
];
|
|
912
1820
|
if (tool.envHint) {
|
|
913
|
-
lines2.push(
|
|
1821
|
+
lines2.push(pc3.yellow(` \u26A0 ${tool.envHint}`));
|
|
914
1822
|
}
|
|
915
1823
|
if (tool.mcp) {
|
|
916
|
-
lines2.push(
|
|
1824
|
+
lines2.push(pc3.dim(" Restart aman-agent to load the new tool."));
|
|
917
1825
|
}
|
|
918
1826
|
return { handled: true, output: lines2.join("\n") };
|
|
919
1827
|
}
|
|
920
1828
|
if (action === "remove") {
|
|
921
1829
|
if (args.length < 1) {
|
|
922
|
-
return { handled: true, output:
|
|
1830
|
+
return { handled: true, output: pc3.yellow("Usage: /akit remove <tool>") };
|
|
923
1831
|
}
|
|
924
1832
|
const toolName = args[0].toLowerCase();
|
|
925
1833
|
if (!installedNames.has(toolName)) {
|
|
926
|
-
return { handled: true, output:
|
|
1834
|
+
return { handled: true, output: pc3.red(`${toolName} is not installed.`) };
|
|
927
1835
|
}
|
|
928
1836
|
const updated = installed.filter((t) => t.name !== toolName);
|
|
929
1837
|
saveAkitInstalled(updated);
|
|
930
1838
|
removeFromAmanAgentConfig(toolName);
|
|
931
1839
|
return {
|
|
932
1840
|
handled: true,
|
|
933
|
-
output:
|
|
1841
|
+
output: pc3.green(`\u2713 Removed ${pc3.bold(toolName)}`) + pc3.dim(" (restart aman-agent to apply)")
|
|
934
1842
|
};
|
|
935
1843
|
}
|
|
936
1844
|
if (action === "help") {
|
|
937
1845
|
return {
|
|
938
1846
|
handled: true,
|
|
939
1847
|
output: [
|
|
940
|
-
|
|
1848
|
+
pc3.bold("akit \u2014 Tool Management"),
|
|
941
1849
|
"",
|
|
942
|
-
` ${
|
|
943
|
-
` ${
|
|
944
|
-
` ${
|
|
1850
|
+
` ${pc3.cyan("/akit")} List installed & available tools`,
|
|
1851
|
+
` ${pc3.cyan("/akit add <tool>")} Install a tool`,
|
|
1852
|
+
` ${pc3.cyan("/akit remove <tool>")} Uninstall a tool`
|
|
945
1853
|
].join("\n")
|
|
946
1854
|
};
|
|
947
1855
|
}
|
|
948
1856
|
const available = AKIT_REGISTRY.filter((t) => !installedNames.has(t.name));
|
|
949
|
-
const lines = [
|
|
1857
|
+
const lines = [pc3.bold("akit \u2014 AI Tool Manager"), ""];
|
|
950
1858
|
if (installed.length > 0) {
|
|
951
|
-
lines.push(` ${
|
|
1859
|
+
lines.push(` ${pc3.bold(`Installed (${installed.length})`)}`);
|
|
952
1860
|
for (const tool of installed) {
|
|
953
|
-
const mcp = tool.mcpConfigured ?
|
|
954
|
-
lines.push(` ${
|
|
1861
|
+
const mcp = tool.mcpConfigured ? pc3.green("MCP") : pc3.dim("manual");
|
|
1862
|
+
lines.push(` ${pc3.green("\u25CF")} ${pc3.bold(tool.name.padEnd(16))} ${mcp} ${pc3.dim(tool.installedAt)}`);
|
|
955
1863
|
}
|
|
956
1864
|
lines.push("");
|
|
957
1865
|
}
|
|
958
1866
|
if (available.length > 0) {
|
|
959
|
-
lines.push(` ${
|
|
1867
|
+
lines.push(` ${pc3.bold(`Available (${available.length})`)}`);
|
|
960
1868
|
const byCategory = /* @__PURE__ */ new Map();
|
|
961
1869
|
for (const tool of available) {
|
|
962
1870
|
if (!byCategory.has(tool.category)) byCategory.set(tool.category, []);
|
|
963
1871
|
byCategory.get(tool.category).push(tool);
|
|
964
1872
|
}
|
|
965
1873
|
for (const [category, tools] of byCategory) {
|
|
966
|
-
lines.push(` ${
|
|
1874
|
+
lines.push(` ${pc3.dim(category)}`);
|
|
967
1875
|
for (const tool of tools) {
|
|
968
|
-
lines.push(` ${
|
|
1876
|
+
lines.push(` ${pc3.dim("\u25CB")} ${tool.name.padEnd(16)} ${pc3.dim(tool.description)}`);
|
|
969
1877
|
}
|
|
970
1878
|
}
|
|
971
1879
|
lines.push("");
|
|
972
1880
|
}
|
|
973
|
-
lines.push(` ${
|
|
974
|
-
lines.push(` ${
|
|
1881
|
+
lines.push(` ${pc3.cyan("/akit add <tool>")} Install a tool`);
|
|
1882
|
+
lines.push(` ${pc3.cyan("/akit remove <tool>")} Uninstall a tool`);
|
|
975
1883
|
return { handled: true, output: lines.join("\n") };
|
|
976
1884
|
}
|
|
977
1885
|
async function handleSkillsCommand(action, args, ctx) {
|
|
978
|
-
const home2 =
|
|
1886
|
+
const home2 = os8.homedir();
|
|
979
1887
|
if (!action) {
|
|
980
|
-
const content = readEcosystemFile(
|
|
1888
|
+
const content = readEcosystemFile(path8.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
981
1889
|
return { handled: true, output: content };
|
|
982
1890
|
}
|
|
983
1891
|
if (action === "install") {
|
|
984
1892
|
if (args.length < 1) {
|
|
985
|
-
return { handled: true, output:
|
|
1893
|
+
return { handled: true, output: pc3.yellow("Usage: /skills install <name>") };
|
|
986
1894
|
}
|
|
987
1895
|
const output = await mcpWrite(ctx, "skills", "skill_install", { name: args.join(" ") });
|
|
988
1896
|
return { handled: true, output };
|
|
989
1897
|
}
|
|
990
1898
|
if (action === "uninstall") {
|
|
991
1899
|
if (args.length < 1) {
|
|
992
|
-
return { handled: true, output:
|
|
1900
|
+
return { handled: true, output: pc3.yellow("Usage: /skills uninstall <name>") };
|
|
993
1901
|
}
|
|
994
1902
|
const output = await mcpWrite(ctx, "skills", "skill_uninstall", { name: args.join(" ") });
|
|
995
1903
|
return { handled: true, output };
|
|
996
1904
|
}
|
|
997
|
-
return { handled: true, output:
|
|
1905
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /skills ${action}. Use /skills [install|uninstall].`) };
|
|
998
1906
|
}
|
|
999
1907
|
async function handleEvalCommand(action, args, ctx) {
|
|
1000
|
-
const home2 =
|
|
1908
|
+
const home2 = os8.homedir();
|
|
1001
1909
|
if (!action) {
|
|
1002
|
-
const content = readEcosystemFile(
|
|
1910
|
+
const content = readEcosystemFile(path8.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
1003
1911
|
return { handled: true, output: content };
|
|
1004
1912
|
}
|
|
1005
1913
|
if (action === "milestone") {
|
|
1006
1914
|
if (args.length < 1) {
|
|
1007
|
-
return { handled: true, output:
|
|
1915
|
+
return { handled: true, output: pc3.yellow("Usage: /eval milestone <text...>") };
|
|
1008
1916
|
}
|
|
1009
1917
|
const text2 = args.join(" ");
|
|
1010
1918
|
const output = await mcpWrite(ctx, "eval", "eval_milestone", { text: text2 });
|
|
1011
1919
|
return { handled: true, output };
|
|
1012
1920
|
}
|
|
1013
|
-
return { handled: true, output:
|
|
1921
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /eval ${action}. Use /eval or /eval milestone <text>.`) };
|
|
1014
1922
|
}
|
|
1015
1923
|
async function handleMemoryCommand(action, args, ctx) {
|
|
1016
1924
|
if (!action) {
|
|
1017
1925
|
if (!ctx.mcpManager) {
|
|
1018
1926
|
return {
|
|
1019
1927
|
handled: true,
|
|
1020
|
-
output:
|
|
1928
|
+
output: pc3.red("Memory not available: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp")
|
|
1021
1929
|
};
|
|
1022
1930
|
}
|
|
1023
1931
|
const result = await ctx.mcpManager.callTool("memory_context", { topic: "recent context" });
|
|
1024
1932
|
if (result.startsWith("Error")) {
|
|
1025
|
-
return { handled: true, output:
|
|
1933
|
+
return { handled: true, output: pc3.red(result) };
|
|
1026
1934
|
}
|
|
1027
1935
|
return { handled: true, output: result };
|
|
1028
1936
|
}
|
|
1029
1937
|
if (action && !["search", "clear", "timeline"].includes(action)) {
|
|
1030
1938
|
if (!ctx.mcpManager) {
|
|
1031
|
-
return { handled: true, output:
|
|
1939
|
+
return { handled: true, output: pc3.red("Memory not available: MCP not connected.") };
|
|
1032
1940
|
}
|
|
1033
1941
|
const topic = [action, ...args].join(" ");
|
|
1034
1942
|
const result = await ctx.mcpManager.callTool("memory_context", { topic });
|
|
1035
1943
|
if (result.startsWith("Error")) {
|
|
1036
|
-
return { handled: true, output:
|
|
1944
|
+
return { handled: true, output: pc3.red(result) };
|
|
1037
1945
|
}
|
|
1038
1946
|
return { handled: true, output: result };
|
|
1039
1947
|
}
|
|
1040
1948
|
if (action === "search") {
|
|
1041
1949
|
if (args.length < 1) {
|
|
1042
|
-
return { handled: true, output:
|
|
1950
|
+
return { handled: true, output: pc3.yellow("Usage: /memory search <query...>") };
|
|
1043
1951
|
}
|
|
1044
1952
|
const query = args.join(" ");
|
|
1045
1953
|
const output = await mcpWrite(ctx, "memory", "memory_recall", { query });
|
|
@@ -1047,19 +1955,19 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
1047
1955
|
}
|
|
1048
1956
|
if (action === "clear") {
|
|
1049
1957
|
if (args.length < 1) {
|
|
1050
|
-
return { handled: true, output:
|
|
1958
|
+
return { handled: true, output: pc3.yellow("Usage: /memory clear <category>") };
|
|
1051
1959
|
}
|
|
1052
1960
|
const output = await mcpWrite(ctx, "memory", "memory_forget", { category: args[0] });
|
|
1053
1961
|
return { handled: true, output };
|
|
1054
1962
|
}
|
|
1055
1963
|
if (action === "timeline") {
|
|
1056
1964
|
if (!ctx.mcpManager) {
|
|
1057
|
-
return { handled: true, output:
|
|
1965
|
+
return { handled: true, output: pc3.red("Memory not available: MCP not connected.") };
|
|
1058
1966
|
}
|
|
1059
1967
|
try {
|
|
1060
1968
|
const result = await ctx.mcpManager.callTool("memory_recall", { query: "*", limit: 500 });
|
|
1061
1969
|
if (result.startsWith("Error") || result.includes("No memories found")) {
|
|
1062
|
-
return { handled: true, output:
|
|
1970
|
+
return { handled: true, output: pc3.dim("No memories yet. Start chatting and I'll remember what matters.") };
|
|
1063
1971
|
}
|
|
1064
1972
|
try {
|
|
1065
1973
|
const memories = JSON.parse(result);
|
|
@@ -1071,7 +1979,7 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
1071
1979
|
}
|
|
1072
1980
|
const maxCount = Math.max(...byDate.values());
|
|
1073
1981
|
const barWidth = 10;
|
|
1074
|
-
const lines = [
|
|
1982
|
+
const lines = [pc3.bold("Memory Timeline:"), ""];
|
|
1075
1983
|
for (const [date, count] of byDate) {
|
|
1076
1984
|
const filled = Math.round(count / maxCount * barWidth);
|
|
1077
1985
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
@@ -1098,64 +2006,64 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
1098
2006
|
const lineCount = result.split("\n").filter((l) => l.trim()).length;
|
|
1099
2007
|
return { handled: true, output: `Total memories: ~${lineCount} entries.` };
|
|
1100
2008
|
} catch {
|
|
1101
|
-
return { handled: true, output:
|
|
2009
|
+
return { handled: true, output: pc3.red("Failed to retrieve memory timeline.") };
|
|
1102
2010
|
}
|
|
1103
2011
|
}
|
|
1104
|
-
return { handled: true, output:
|
|
2012
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /memory ${action}. Use /memory [search|clear|timeline].`) };
|
|
1105
2013
|
}
|
|
1106
2014
|
function handleStatusCommand(ctx) {
|
|
1107
2015
|
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
1108
2016
|
const amemConnected = mcpToolCount > 0;
|
|
1109
2017
|
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
1110
|
-
const lines = [
|
|
2018
|
+
const lines = [pc3.bold("Aman Ecosystem Dashboard"), ""];
|
|
1111
2019
|
for (const layer of status.layers) {
|
|
1112
|
-
const icon = layer.exists ?
|
|
1113
|
-
const name =
|
|
1114
|
-
const summary = layer.exists ? layer.summary :
|
|
2020
|
+
const icon = layer.exists ? pc3.green("\u25CF") : pc3.dim("\u25CB");
|
|
2021
|
+
const name = pc3.bold(layer.name.padEnd(12));
|
|
2022
|
+
const summary = layer.exists ? layer.summary : pc3.dim("not configured");
|
|
1115
2023
|
lines.push(` ${icon} ${name} ${summary}`);
|
|
1116
2024
|
}
|
|
1117
2025
|
lines.push("");
|
|
1118
|
-
lines.push(` ${status.mcpConnected ?
|
|
1119
|
-
lines.push(` ${status.amemConnected ?
|
|
2026
|
+
lines.push(` ${status.mcpConnected ? pc3.green("\u25CF") : pc3.dim("\u25CB")} ${pc3.bold("MCP".padEnd(12))} ${status.mcpConnected ? `${status.mcpToolCount} tools available` : pc3.dim("not connected")}`);
|
|
2027
|
+
lines.push(` ${status.amemConnected ? pc3.green("\u25CF") : pc3.dim("\u25CB")} ${pc3.bold("Memory".padEnd(12))} ${status.amemConnected ? "connected" : pc3.dim("not connected")}`);
|
|
1120
2028
|
return { handled: true, output: lines.join("\n") };
|
|
1121
2029
|
}
|
|
1122
2030
|
function handleDoctorCommand(ctx) {
|
|
1123
2031
|
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
1124
2032
|
const amemConnected = mcpToolCount > 0;
|
|
1125
2033
|
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
1126
|
-
const lines = [
|
|
2034
|
+
const lines = [pc3.bold("Aman Health Check"), ""];
|
|
1127
2035
|
let healthy = 0;
|
|
1128
2036
|
let fixes = 0;
|
|
1129
2037
|
let suggestions = 0;
|
|
1130
2038
|
for (const layer of status.layers) {
|
|
1131
2039
|
if (layer.exists) {
|
|
1132
|
-
lines.push(` ${
|
|
2040
|
+
lines.push(` ${pc3.green("\u2713")} ${layer.name.padEnd(12)} ${pc3.green(layer.summary)}`);
|
|
1133
2041
|
healthy++;
|
|
1134
2042
|
} else {
|
|
1135
2043
|
const isRequired = ["identity", "rules"].includes(layer.name.toLowerCase());
|
|
1136
2044
|
if (isRequired) {
|
|
1137
|
-
lines.push(` ${
|
|
1138
|
-
lines.push(` ${
|
|
2045
|
+
lines.push(` ${pc3.red("\u2717")} ${layer.name.padEnd(12)} ${pc3.red("missing")}`);
|
|
2046
|
+
lines.push(` ${pc3.dim("\u2192 Fix: aman-agent init")}`);
|
|
1139
2047
|
fixes++;
|
|
1140
2048
|
} else {
|
|
1141
|
-
lines.push(` ${
|
|
2049
|
+
lines.push(` ${pc3.yellow("\u26A0")} ${layer.name.padEnd(12)} ${pc3.yellow("empty")}`);
|
|
1142
2050
|
const cmd = layer.name.toLowerCase() === "workflows" ? "/workflows add <name>" : layer.name.toLowerCase() === "tools" ? "/tools add <name> <type> <desc>" : layer.name.toLowerCase() === "skills" ? "/skills install <name>" : "";
|
|
1143
|
-
if (cmd) lines.push(` ${
|
|
2051
|
+
if (cmd) lines.push(` ${pc3.dim(`\u2192 Add with ${cmd}`)}`);
|
|
1144
2052
|
suggestions++;
|
|
1145
2053
|
}
|
|
1146
2054
|
}
|
|
1147
2055
|
}
|
|
1148
2056
|
lines.push("");
|
|
1149
|
-
lines.push(` ${status.mcpConnected ?
|
|
2057
|
+
lines.push(` ${status.mcpConnected ? pc3.green("\u2713") : pc3.red("\u2717")} ${"MCP".padEnd(12)} ${status.mcpConnected ? pc3.green(`${status.mcpToolCount} tools`) : pc3.red("not connected")}`);
|
|
1150
2058
|
if (!status.mcpConnected) {
|
|
1151
|
-
lines.push(` ${
|
|
2059
|
+
lines.push(` ${pc3.dim("\u2192 Fix: ensure npx is available and network is connected")}`);
|
|
1152
2060
|
fixes++;
|
|
1153
2061
|
} else {
|
|
1154
2062
|
healthy++;
|
|
1155
2063
|
}
|
|
1156
|
-
lines.push(` ${status.amemConnected ?
|
|
2064
|
+
lines.push(` ${status.amemConnected ? pc3.green("\u2713") : pc3.red("\u2717")} ${"Memory".padEnd(12)} ${status.amemConnected ? pc3.green("connected") : pc3.red("not connected")}`);
|
|
1157
2065
|
if (!status.amemConnected) {
|
|
1158
|
-
lines.push(` ${
|
|
2066
|
+
lines.push(` ${pc3.dim("\u2192 Fix: npx @aman_asmuei/amem")}`);
|
|
1159
2067
|
fixes++;
|
|
1160
2068
|
} else {
|
|
1161
2069
|
healthy++;
|
|
@@ -1169,26 +2077,26 @@ function handleHelp() {
|
|
|
1169
2077
|
return {
|
|
1170
2078
|
handled: true,
|
|
1171
2079
|
output: [
|
|
1172
|
-
|
|
1173
|
-
` ${
|
|
1174
|
-
` ${
|
|
1175
|
-
` ${
|
|
1176
|
-
` ${
|
|
1177
|
-
` ${
|
|
1178
|
-
` ${
|
|
1179
|
-
` ${
|
|
1180
|
-
` ${
|
|
1181
|
-
` ${
|
|
1182
|
-
` ${
|
|
1183
|
-
` ${
|
|
1184
|
-
` ${
|
|
1185
|
-
` ${
|
|
1186
|
-
` ${
|
|
1187
|
-
` ${
|
|
1188
|
-
` ${
|
|
1189
|
-
` ${
|
|
1190
|
-
` ${
|
|
1191
|
-
` ${
|
|
2080
|
+
pc3.bold("Commands:"),
|
|
2081
|
+
` ${pc3.cyan("/help")} Show this help`,
|
|
2082
|
+
` ${pc3.cyan("/identity")} View identity [update <section>]`,
|
|
2083
|
+
` ${pc3.cyan("/rules")} View rules [add|remove|toggle ...]`,
|
|
2084
|
+
` ${pc3.cyan("/workflows")} View workflows [add|remove ...]`,
|
|
2085
|
+
` ${pc3.cyan("/akit")} Manage tools [add|remove <tool>]`,
|
|
2086
|
+
` ${pc3.cyan("/skills")} View skills [install|uninstall ...]`,
|
|
2087
|
+
` ${pc3.cyan("/eval")} View evaluation [milestone ...]`,
|
|
2088
|
+
` ${pc3.cyan("/memory")} View recent memories [search|clear|timeline]`,
|
|
2089
|
+
` ${pc3.cyan("/status")} Ecosystem dashboard`,
|
|
2090
|
+
` ${pc3.cyan("/doctor")} Health check all layers`,
|
|
2091
|
+
` ${pc3.cyan("/decisions")} View decision log [<project>]`,
|
|
2092
|
+
` ${pc3.cyan("/export")} Export conversation to markdown`,
|
|
2093
|
+
` ${pc3.cyan("/debug")} Show debug log`,
|
|
2094
|
+
` ${pc3.cyan("/save")} Save conversation to memory`,
|
|
2095
|
+
` ${pc3.cyan("/model")} Show current LLM model`,
|
|
2096
|
+
` ${pc3.cyan("/update")} Check for updates`,
|
|
2097
|
+
` ${pc3.cyan("/reconfig")} Reset LLM config`,
|
|
2098
|
+
` ${pc3.cyan("/clear")} Clear conversation history`,
|
|
2099
|
+
` ${pc3.cyan("/quit")} Exit`
|
|
1192
2100
|
].join("\n")
|
|
1193
2101
|
};
|
|
1194
2102
|
}
|
|
@@ -1196,18 +2104,18 @@ function handleSave() {
|
|
|
1196
2104
|
return { handled: true, saveConversation: true };
|
|
1197
2105
|
}
|
|
1198
2106
|
function handleReconfig() {
|
|
1199
|
-
const configDir =
|
|
1200
|
-
const configPath =
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
2107
|
+
const configDir = path8.join(os8.homedir(), ".aman-agent");
|
|
2108
|
+
const configPath = path8.join(configDir, "config.json");
|
|
2109
|
+
if (fs8.existsSync(configPath)) {
|
|
2110
|
+
fs8.unlinkSync(configPath);
|
|
1203
2111
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
2112
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
2113
|
+
fs8.writeFileSync(path8.join(configDir, ".reconfig"), "", "utf-8");
|
|
1206
2114
|
return {
|
|
1207
2115
|
handled: true,
|
|
1208
2116
|
quit: true,
|
|
1209
2117
|
output: [
|
|
1210
|
-
|
|
2118
|
+
pc3.green("Config reset."),
|
|
1211
2119
|
"Next run will prompt you to choose your LLM provider."
|
|
1212
2120
|
].join("\n")
|
|
1213
2121
|
};
|
|
@@ -1215,20 +2123,20 @@ function handleReconfig() {
|
|
|
1215
2123
|
function handleUpdate() {
|
|
1216
2124
|
try {
|
|
1217
2125
|
const current = execFileSync("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
1218
|
-
const local = JSON.parse(
|
|
2126
|
+
const local = JSON.parse(fs8.readFileSync(path8.join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
1219
2127
|
if (current === local) {
|
|
1220
|
-
return { handled: true, output: `${
|
|
2128
|
+
return { handled: true, output: `${pc3.green("Up to date")} \u2014 v${local}` };
|
|
1221
2129
|
}
|
|
1222
2130
|
return {
|
|
1223
2131
|
handled: true,
|
|
1224
2132
|
output: [
|
|
1225
|
-
`${
|
|
2133
|
+
`${pc3.yellow("Update available:")} v${local} \u2192 v${current}`,
|
|
1226
2134
|
"",
|
|
1227
2135
|
`Run this in your terminal:`,
|
|
1228
|
-
` ${
|
|
2136
|
+
` ${pc3.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
1229
2137
|
"",
|
|
1230
2138
|
`Or use npx (always latest):`,
|
|
1231
|
-
` ${
|
|
2139
|
+
` ${pc3.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
1232
2140
|
].join("\n")
|
|
1233
2141
|
};
|
|
1234
2142
|
} catch {
|
|
@@ -1236,17 +2144,17 @@ function handleUpdate() {
|
|
|
1236
2144
|
handled: true,
|
|
1237
2145
|
output: [
|
|
1238
2146
|
`To update, run in your terminal:`,
|
|
1239
|
-
` ${
|
|
2147
|
+
` ${pc3.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
1240
2148
|
"",
|
|
1241
2149
|
`Or use npx (always latest):`,
|
|
1242
|
-
` ${
|
|
2150
|
+
` ${pc3.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
1243
2151
|
].join("\n")
|
|
1244
2152
|
};
|
|
1245
2153
|
}
|
|
1246
2154
|
}
|
|
1247
2155
|
async function handleDecisionsCommand(action, _args, ctx) {
|
|
1248
2156
|
if (!ctx.mcpManager) {
|
|
1249
|
-
return { handled: true, output:
|
|
2157
|
+
return { handled: true, output: pc3.red("Decisions not available: MCP not connected.") };
|
|
1250
2158
|
}
|
|
1251
2159
|
const scope = action || void 0;
|
|
1252
2160
|
const result = await ctx.mcpManager.callTool("memory_recall", {
|
|
@@ -1256,22 +2164,407 @@ async function handleDecisionsCommand(action, _args, ctx) {
|
|
|
1256
2164
|
...scope ? { scope } : {}
|
|
1257
2165
|
});
|
|
1258
2166
|
if (result.startsWith("Error")) {
|
|
1259
|
-
return { handled: true, output:
|
|
2167
|
+
return { handled: true, output: pc3.red(result) };
|
|
1260
2168
|
}
|
|
1261
|
-
return { handled: true, output:
|
|
2169
|
+
return { handled: true, output: pc3.bold("Decision Log:\n") + result };
|
|
1262
2170
|
}
|
|
1263
2171
|
function handleExportCommand() {
|
|
1264
2172
|
return { handled: true, exportConversation: true };
|
|
1265
2173
|
}
|
|
1266
2174
|
function handleDebugCommand() {
|
|
1267
|
-
const logPath =
|
|
1268
|
-
if (!
|
|
1269
|
-
return { handled: true, output:
|
|
2175
|
+
const logPath = path8.join(os8.homedir(), ".aman-agent", "debug.log");
|
|
2176
|
+
if (!fs8.existsSync(logPath)) {
|
|
2177
|
+
return { handled: true, output: pc3.dim("No debug log found.") };
|
|
2178
|
+
}
|
|
2179
|
+
const content = fs8.readFileSync(logPath, "utf-8");
|
|
2180
|
+
const lines = content.trim().split("\n");
|
|
2181
|
+
const last20 = lines.slice(-20).join("\n");
|
|
2182
|
+
return { handled: true, output: pc3.bold("Debug Log (last 20 entries):\n") + pc3.dim(last20) };
|
|
2183
|
+
}
|
|
2184
|
+
async function handleTeamCommand(action, args, ctx) {
|
|
2185
|
+
if (!action || action === "list") {
|
|
2186
|
+
const teams = listTeams();
|
|
2187
|
+
if (teams.length === 0) {
|
|
2188
|
+
return {
|
|
2189
|
+
handled: true,
|
|
2190
|
+
output: pc3.dim("No teams yet. Create one:") + "\n /team create <name> Create from built-in template\n /team create Show available templates"
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
const lines = teams.map((t) => {
|
|
2194
|
+
const members = t.members.map((m) => m.profile).join(", ");
|
|
2195
|
+
return ` ${pc3.bold(t.name)} (${t.workflow}) \u2014 ${members}`;
|
|
2196
|
+
});
|
|
2197
|
+
return { handled: true, output: "Teams:\n" + lines.join("\n") };
|
|
2198
|
+
}
|
|
2199
|
+
switch (action) {
|
|
2200
|
+
case "create": {
|
|
2201
|
+
const name = args[0];
|
|
2202
|
+
if (!name) {
|
|
2203
|
+
const lines = BUILT_IN_TEAMS.map((t) => {
|
|
2204
|
+
const members2 = t.members.map((m) => m.profile).join(" \u2192 ");
|
|
2205
|
+
return ` ${pc3.bold(t.name)} (${t.workflow}) \u2014 ${members2}
|
|
2206
|
+
${pc3.dim(t.goal)}`;
|
|
2207
|
+
});
|
|
2208
|
+
return {
|
|
2209
|
+
handled: true,
|
|
2210
|
+
output: "Built-in teams:\n" + lines.join("\n\n") + "\n\nUsage:\n /team create content-team Install built-in\n /team create <name> <mode> <profile1:role>,<profile2:role> Custom"
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
const builtIn = BUILT_IN_TEAMS.find((t) => t.name === name);
|
|
2214
|
+
if (builtIn) {
|
|
2215
|
+
createTeam(builtIn);
|
|
2216
|
+
return { handled: true, output: pc3.green(`Team installed: ${builtIn.name}`) + "\n\n" + formatTeam(builtIn) };
|
|
2217
|
+
}
|
|
2218
|
+
const mode = args[1];
|
|
2219
|
+
const membersStr = args[2];
|
|
2220
|
+
if (!mode || !membersStr) {
|
|
2221
|
+
return { handled: true, output: pc3.yellow("Usage: /team create <name> <pipeline|parallel|coordinator> <profile1:role>,<profile2:role>") };
|
|
2222
|
+
}
|
|
2223
|
+
if (!["pipeline", "parallel", "coordinator"].includes(mode)) {
|
|
2224
|
+
return { handled: true, output: pc3.yellow("Mode must be: pipeline, parallel, or coordinator") };
|
|
2225
|
+
}
|
|
2226
|
+
const members = membersStr.split(",").map((m) => {
|
|
2227
|
+
const [profile, ...roleParts] = m.trim().split(":");
|
|
2228
|
+
return { profile: profile.trim(), role: roleParts.join(":").trim() || profile.trim() };
|
|
2229
|
+
});
|
|
2230
|
+
const team = {
|
|
2231
|
+
name,
|
|
2232
|
+
goal: `Team: ${name}`,
|
|
2233
|
+
coordinator: "default",
|
|
2234
|
+
members,
|
|
2235
|
+
workflow: mode
|
|
2236
|
+
};
|
|
2237
|
+
createTeam(team);
|
|
2238
|
+
return { handled: true, output: pc3.green(`Team created!`) + "\n\n" + formatTeam(team) };
|
|
2239
|
+
}
|
|
2240
|
+
case "run": {
|
|
2241
|
+
const teamName = args[0];
|
|
2242
|
+
const task = args.slice(1).join(" ");
|
|
2243
|
+
if (!teamName || !task) {
|
|
2244
|
+
return { handled: true, output: pc3.yellow("Usage: /team run <team-name> <task description>") };
|
|
2245
|
+
}
|
|
2246
|
+
const team = loadTeam(teamName);
|
|
2247
|
+
if (!team) return { handled: true, output: pc3.red(`Team not found: ${teamName}`) };
|
|
2248
|
+
if (!ctx.llmClient || !ctx.mcpManager) {
|
|
2249
|
+
return { handled: true, output: pc3.red("Team execution requires LLM client and MCP.") };
|
|
2250
|
+
}
|
|
2251
|
+
const result = await runTeam(team, task, ctx.llmClient, ctx.mcpManager, ctx.tools);
|
|
2252
|
+
return { handled: true, output: formatTeamResult(result) };
|
|
2253
|
+
}
|
|
2254
|
+
case "show": {
|
|
2255
|
+
const name = args[0];
|
|
2256
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /team show <name>") };
|
|
2257
|
+
const team = loadTeam(name);
|
|
2258
|
+
if (!team) return { handled: true, output: pc3.red(`Team not found: ${name}`) };
|
|
2259
|
+
return { handled: true, output: formatTeam(team) };
|
|
2260
|
+
}
|
|
2261
|
+
case "delete": {
|
|
2262
|
+
const name = args[0];
|
|
2263
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /team delete <name>") };
|
|
2264
|
+
if (!deleteTeam(name)) return { handled: true, output: pc3.red(`Team not found: ${name}`) };
|
|
2265
|
+
return { handled: true, output: pc3.dim(`Team deleted: ${name}`) };
|
|
2266
|
+
}
|
|
2267
|
+
case "help":
|
|
2268
|
+
return { handled: true, output: `Team commands:
|
|
2269
|
+
/team List all teams
|
|
2270
|
+
/team create Show built-in templates
|
|
2271
|
+
/team create <name> Install built-in team
|
|
2272
|
+
/team create <n> <mode> <m> Custom team (mode: pipeline|parallel|coordinator)
|
|
2273
|
+
/team run <name> <task> Run a task with a team
|
|
2274
|
+
/team show <name> Show team details
|
|
2275
|
+
/team delete <name> Delete a team
|
|
2276
|
+
|
|
2277
|
+
Modes:
|
|
2278
|
+
pipeline Sequential: agent1 \u2192 agent2 \u2192 agent3
|
|
2279
|
+
parallel All agents work concurrently, coordinator merges
|
|
2280
|
+
coordinator Coordinator LLM decides how to split the task
|
|
2281
|
+
|
|
2282
|
+
Examples:
|
|
2283
|
+
/team create content-team
|
|
2284
|
+
/team run content-team Write a blog post about AI companions
|
|
2285
|
+
/team create review-squad pipeline coder:implement,researcher:review
|
|
2286
|
+
/team run review-squad Build a rate limiter in TypeScript` };
|
|
2287
|
+
default:
|
|
2288
|
+
return { handled: true, output: pc3.yellow(`Unknown team action: ${action}. Try /team help`) };
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
async function handleDelegateCommand(action, args, ctx) {
|
|
2292
|
+
if (!action) {
|
|
2293
|
+
return { handled: true, output: `Delegate commands:
|
|
2294
|
+
/delegate <profile> <task> Delegate a task to a profile
|
|
2295
|
+
/delegate pipeline <p1> <p2> ... Run a sequential pipeline
|
|
2296
|
+
/delegate help Show help
|
|
2297
|
+
|
|
2298
|
+
Examples:
|
|
2299
|
+
/delegate writer Write a blog post about AI companions
|
|
2300
|
+
/delegate coder Review this code for security issues
|
|
2301
|
+
/delegate pipeline writer,researcher Write and fact-check an article about quantum computing` };
|
|
2302
|
+
}
|
|
2303
|
+
if (action === "help") {
|
|
2304
|
+
return { handled: true, output: `Delegate a task to a sub-agent with a specific profile.
|
|
2305
|
+
|
|
2306
|
+
The sub-agent runs with its own identity, rules, and skills but shares
|
|
2307
|
+
your memory and tools. Results come back to you.
|
|
2308
|
+
|
|
2309
|
+
Usage:
|
|
2310
|
+
/delegate <profile> <task>
|
|
2311
|
+
/delegate pipeline <profile1>,<profile2> <task>
|
|
2312
|
+
|
|
2313
|
+
The pipeline mode passes each agent's output to the next:
|
|
2314
|
+
writer drafts \u2192 researcher reviews \u2192 writer polishes` };
|
|
2315
|
+
}
|
|
2316
|
+
if (!ctx.llmClient || !ctx.mcpManager) {
|
|
2317
|
+
return { handled: true, output: pc3.red("Delegation requires LLM client and MCP. Not available.") };
|
|
2318
|
+
}
|
|
2319
|
+
if (action === "pipeline") {
|
|
2320
|
+
const profileList = args[0];
|
|
2321
|
+
const task2 = args.slice(1).join(" ");
|
|
2322
|
+
if (!profileList || !task2) {
|
|
2323
|
+
return { handled: true, output: pc3.yellow("Usage: /delegate pipeline <profile1>,<profile2> <task>") };
|
|
2324
|
+
}
|
|
2325
|
+
const profiles = profileList.split(",").map((p3) => p3.trim());
|
|
2326
|
+
const steps = profiles.map((profile2, i) => {
|
|
2327
|
+
if (i === 0) {
|
|
2328
|
+
return { profile: profile2, taskTemplate: task2 };
|
|
2329
|
+
}
|
|
2330
|
+
return { profile: profile2, taskTemplate: `Review and improve the following:
|
|
2331
|
+
|
|
2332
|
+
{{input}}` };
|
|
2333
|
+
});
|
|
2334
|
+
process.stdout.write(pc3.dim(`
|
|
2335
|
+
Pipeline: ${profiles.join(" \u2192 ")}
|
|
2336
|
+
`));
|
|
2337
|
+
const results = await delegatePipeline(steps, task2, ctx.llmClient, ctx.mcpManager, { tools: ctx.tools });
|
|
2338
|
+
const output = [];
|
|
2339
|
+
for (const r of results) {
|
|
2340
|
+
if (r.success) {
|
|
2341
|
+
output.push(`
|
|
2342
|
+
${pc3.bold(`[${r.profile}]`)} ${pc3.green("\u2713")} (${r.turns} tool turns)`);
|
|
2343
|
+
output.push(r.response.slice(0, 2e3));
|
|
2344
|
+
if (r.toolsUsed.length > 0) output.push(pc3.dim(` Tools: ${r.toolsUsed.join(", ")}`));
|
|
2345
|
+
} else {
|
|
2346
|
+
output.push(`
|
|
2347
|
+
${pc3.bold(`[${r.profile}]`)} ${pc3.red("\u2717")} ${r.error}`);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return { handled: true, output: output.join("\n") };
|
|
2351
|
+
}
|
|
2352
|
+
const profile = action;
|
|
2353
|
+
const task = args.join(" ");
|
|
2354
|
+
if (!task) {
|
|
2355
|
+
return { handled: true, output: pc3.yellow(`Usage: /delegate ${profile} <task description>`) };
|
|
2356
|
+
}
|
|
2357
|
+
process.stdout.write(pc3.dim(`
|
|
2358
|
+
[delegating to ${profile}...]
|
|
2359
|
+
|
|
2360
|
+
`));
|
|
2361
|
+
const result = await delegateTask(task, profile, ctx.llmClient, ctx.mcpManager, { tools: ctx.tools });
|
|
2362
|
+
if (!result.success) {
|
|
2363
|
+
return { handled: true, output: pc3.red(`Delegation failed: ${result.error}`) };
|
|
2364
|
+
}
|
|
2365
|
+
const meta = [];
|
|
2366
|
+
if (result.toolsUsed.length > 0) meta.push(`Tools: ${result.toolsUsed.join(", ")}`);
|
|
2367
|
+
if (result.turns > 0) meta.push(`${result.turns} tool turns`);
|
|
2368
|
+
return {
|
|
2369
|
+
handled: true,
|
|
2370
|
+
output: `
|
|
2371
|
+
${pc3.bold(`[${profile}]`)} ${pc3.green("\u2713")}${meta.length > 0 ? " " + pc3.dim(`(${meta.join(", ")})`) : ""}
|
|
2372
|
+
|
|
2373
|
+
${result.response}`
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
function handleProfileCommand(action, args) {
|
|
2377
|
+
const profilesDir = path8.join(os8.homedir(), ".acore", "profiles");
|
|
2378
|
+
if (!action || action === "list") {
|
|
2379
|
+
const profiles = listProfiles();
|
|
2380
|
+
if (profiles.length === 0) {
|
|
2381
|
+
return { handled: true, output: pc3.dim("No profiles yet. Create one with: /profile create <name>") };
|
|
2382
|
+
}
|
|
2383
|
+
const lines = profiles.map(
|
|
2384
|
+
(p3) => ` ${pc3.bold(p3.name)} \u2014 ${p3.aiName} (${pc3.dim(p3.personality)})`
|
|
2385
|
+
);
|
|
2386
|
+
return { handled: true, output: "Profiles:\n" + lines.join("\n") + "\n\n" + pc3.dim("Switch with: aman-agent --profile <name>") };
|
|
2387
|
+
}
|
|
2388
|
+
switch (action) {
|
|
2389
|
+
case "create": {
|
|
2390
|
+
const name = args[0];
|
|
2391
|
+
if (!name) {
|
|
2392
|
+
const lines = BUILT_IN_PROFILES.map(
|
|
2393
|
+
(t) => ` ${pc3.bold(t.name)} \u2014 ${t.label}: ${pc3.dim(t.description)}`
|
|
2394
|
+
);
|
|
2395
|
+
return {
|
|
2396
|
+
handled: true,
|
|
2397
|
+
output: "Built-in profiles:\n" + lines.join("\n") + "\n\nUsage:\n /profile create coder Install built-in template\n /profile create <custom> Create blank profile"
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2401
|
+
const profileDir = path8.join(profilesDir, slug);
|
|
2402
|
+
if (fs8.existsSync(profileDir)) {
|
|
2403
|
+
return { handled: true, output: pc3.yellow(`Profile already exists: ${slug}`) };
|
|
2404
|
+
}
|
|
2405
|
+
const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
|
|
2406
|
+
if (builtIn) {
|
|
2407
|
+
const err = installProfileTemplate(slug);
|
|
2408
|
+
if (err) return { handled: true, output: pc3.red(err) };
|
|
2409
|
+
return {
|
|
2410
|
+
handled: true,
|
|
2411
|
+
output: pc3.green(`Profile installed: ${builtIn.label}`) + `
|
|
2412
|
+
AI name: ${builtIn.core.match(/^# (.+)/m)?.[1] || slug}
|
|
2413
|
+
${pc3.dim(builtIn.description)}
|
|
2414
|
+
|
|
2415
|
+
Use: aman-agent --profile ${slug}`
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
fs8.mkdirSync(profileDir, { recursive: true });
|
|
2419
|
+
const globalCore = path8.join(os8.homedir(), ".acore", "core.md");
|
|
2420
|
+
if (fs8.existsSync(globalCore)) {
|
|
2421
|
+
let content = fs8.readFileSync(globalCore, "utf-8");
|
|
2422
|
+
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2423
|
+
content = content.replace(/^# .+$/m, `# ${aiName}`);
|
|
2424
|
+
fs8.writeFileSync(path8.join(profileDir, "core.md"), content, "utf-8");
|
|
2425
|
+
} else {
|
|
2426
|
+
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2427
|
+
fs8.writeFileSync(path8.join(profileDir, "core.md"), `# ${aiName}
|
|
2428
|
+
|
|
2429
|
+
## Identity
|
|
2430
|
+
- Role: ${aiName} is your AI companion
|
|
2431
|
+
- Personality: helpful, adaptive
|
|
2432
|
+
- Communication: clear and concise
|
|
2433
|
+
- Values: honesty, simplicity
|
|
2434
|
+
- Boundaries: won't pretend to be human
|
|
2435
|
+
`, "utf-8");
|
|
2436
|
+
}
|
|
2437
|
+
return {
|
|
2438
|
+
handled: true,
|
|
2439
|
+
output: pc3.green(`Profile created: ${slug}`) + `
|
|
2440
|
+
Edit: ${path8.join(profileDir, "core.md")}
|
|
2441
|
+
Use: aman-agent --profile ${slug}
|
|
2442
|
+
|
|
2443
|
+
${pc3.dim("Add rules.md or skills.md for profile-specific overrides.")}`
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
case "show": {
|
|
2447
|
+
const name = args[0];
|
|
2448
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /profile show <name>") };
|
|
2449
|
+
const profileDir = path8.join(profilesDir, name);
|
|
2450
|
+
if (!fs8.existsSync(profileDir)) return { handled: true, output: pc3.red(`Profile not found: ${name}`) };
|
|
2451
|
+
const files = fs8.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
|
|
2452
|
+
const lines = files.map((f) => ` ${f}`);
|
|
2453
|
+
return { handled: true, output: `Profile: ${pc3.bold(name)}
|
|
2454
|
+
Files:
|
|
2455
|
+
${lines.join("\n")}` };
|
|
2456
|
+
}
|
|
2457
|
+
case "delete": {
|
|
2458
|
+
const name = args[0];
|
|
2459
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /profile delete <name>") };
|
|
2460
|
+
const profileDir = path8.join(profilesDir, name);
|
|
2461
|
+
if (!fs8.existsSync(profileDir)) return { handled: true, output: pc3.red(`Profile not found: ${name}`) };
|
|
2462
|
+
fs8.rmSync(profileDir, { recursive: true });
|
|
2463
|
+
return { handled: true, output: pc3.dim(`Profile deleted: ${name}`) };
|
|
2464
|
+
}
|
|
2465
|
+
case "help":
|
|
2466
|
+
return { handled: true, output: `Profile commands:
|
|
2467
|
+
/profile List all profiles
|
|
2468
|
+
/profile create <n> Create new profile
|
|
2469
|
+
/profile show <n> Show profile files
|
|
2470
|
+
/profile delete <n> Delete a profile
|
|
2471
|
+
|
|
2472
|
+
Use profiles:
|
|
2473
|
+
aman-agent --profile <name>
|
|
2474
|
+
AMAN_PROFILE=<name> aman-agent` };
|
|
2475
|
+
default:
|
|
2476
|
+
return { handled: true, output: pc3.yellow(`Unknown profile action: ${action}. Try /profile help`) };
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function handlePlanCommand(action, args) {
|
|
2480
|
+
if (!action) {
|
|
2481
|
+
const active = getActivePlan();
|
|
2482
|
+
if (!active) {
|
|
2483
|
+
return { handled: true, output: pc3.dim("No active plan. Create one with: /plan create <name> | <goal> | <step1>, <step2>, ...") };
|
|
2484
|
+
}
|
|
2485
|
+
return { handled: true, output: formatPlan(active) };
|
|
2486
|
+
}
|
|
2487
|
+
switch (action) {
|
|
2488
|
+
case "create": {
|
|
2489
|
+
const fullArgs = args.join(" ");
|
|
2490
|
+
const parts = fullArgs.split("|").map((p3) => p3.trim());
|
|
2491
|
+
if (parts.length < 3) {
|
|
2492
|
+
return { handled: true, output: pc3.yellow("Usage: /plan create <name> | <goal> | <step1>, <step2>, ...") };
|
|
2493
|
+
}
|
|
2494
|
+
const name = parts[0];
|
|
2495
|
+
const goal = parts[1];
|
|
2496
|
+
const steps = parts[2].split(",").map((s) => s.trim()).filter(Boolean);
|
|
2497
|
+
if (steps.length === 0) {
|
|
2498
|
+
return { handled: true, output: pc3.yellow("Need at least one step. Separate steps with commas.") };
|
|
2499
|
+
}
|
|
2500
|
+
const plan = createPlan(name, goal, steps);
|
|
2501
|
+
return { handled: true, output: pc3.green(`Plan created!
|
|
2502
|
+
|
|
2503
|
+
`) + formatPlan(plan) };
|
|
2504
|
+
}
|
|
2505
|
+
case "done": {
|
|
2506
|
+
const active = getActivePlan();
|
|
2507
|
+
if (!active) return { handled: true, output: pc3.yellow("No active plan.") };
|
|
2508
|
+
if (args.length > 0) {
|
|
2509
|
+
const stepNum = parseInt(args[0], 10);
|
|
2510
|
+
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
2511
|
+
return { handled: true, output: pc3.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
2512
|
+
}
|
|
2513
|
+
markStepDone(active, stepNum - 1);
|
|
2514
|
+
return { handled: true, output: pc3.green(`Step ${stepNum} done!`) + "\n\n" + formatPlan(active) };
|
|
2515
|
+
}
|
|
2516
|
+
const next = active.steps.findIndex((s) => !s.done);
|
|
2517
|
+
if (next < 0) return { handled: true, output: pc3.green("All steps already complete!") };
|
|
2518
|
+
markStepDone(active, next);
|
|
2519
|
+
return { handled: true, output: pc3.green(`Step ${next + 1} done!`) + "\n\n" + formatPlan(active) };
|
|
2520
|
+
}
|
|
2521
|
+
case "undo": {
|
|
2522
|
+
const active = getActivePlan();
|
|
2523
|
+
if (!active) return { handled: true, output: pc3.yellow("No active plan.") };
|
|
2524
|
+
const stepNum = parseInt(args[0], 10);
|
|
2525
|
+
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
2526
|
+
return { handled: true, output: pc3.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
2527
|
+
}
|
|
2528
|
+
markStepUndone(active, stepNum - 1);
|
|
2529
|
+
return { handled: true, output: pc3.dim(`Step ${stepNum} unmarked.`) + "\n\n" + formatPlan(active) };
|
|
2530
|
+
}
|
|
2531
|
+
case "list": {
|
|
2532
|
+
const plans = listPlans();
|
|
2533
|
+
if (plans.length === 0) return { handled: true, output: pc3.dim("No plans yet.") };
|
|
2534
|
+
const lines = plans.map((p3) => {
|
|
2535
|
+
const done = p3.steps.filter((s) => s.done).length;
|
|
2536
|
+
const total = p3.steps.length;
|
|
2537
|
+
const status = p3.active ? pc3.green("active") : pc3.dim("inactive");
|
|
2538
|
+
return ` ${p3.name} \u2014 ${done}/${total} steps (${status})`;
|
|
2539
|
+
});
|
|
2540
|
+
return { handled: true, output: "Plans:\n" + lines.join("\n") };
|
|
2541
|
+
}
|
|
2542
|
+
case "switch": {
|
|
2543
|
+
const name = args.join(" ");
|
|
2544
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /plan switch <name>") };
|
|
2545
|
+
const plan = setActivePlan(name);
|
|
2546
|
+
if (!plan) return { handled: true, output: pc3.red(`Plan not found: ${name}`) };
|
|
2547
|
+
return { handled: true, output: pc3.green(`Switched to: ${plan.name}`) + "\n\n" + formatPlan(plan) };
|
|
2548
|
+
}
|
|
2549
|
+
case "show": {
|
|
2550
|
+
const name = args.join(" ");
|
|
2551
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /plan show <name>") };
|
|
2552
|
+
const plan = loadPlan(name);
|
|
2553
|
+
if (!plan) return { handled: true, output: pc3.red(`Plan not found: ${name}`) };
|
|
2554
|
+
return { handled: true, output: formatPlan(plan) };
|
|
2555
|
+
}
|
|
2556
|
+
case "help":
|
|
2557
|
+
return { handled: true, output: `Plan commands:
|
|
2558
|
+
/plan Show active plan
|
|
2559
|
+
/plan create <name> | <goal> | <step1>, <step2>, ...
|
|
2560
|
+
/plan done [step#] Mark step complete (next if no number)
|
|
2561
|
+
/plan undo <step#> Unmark a step
|
|
2562
|
+
/plan list List all plans
|
|
2563
|
+
/plan switch <name> Switch active plan
|
|
2564
|
+
/plan show <name> Show a specific plan` };
|
|
2565
|
+
default:
|
|
2566
|
+
return { handled: true, output: pc3.yellow(`Unknown plan action: ${action}. Try /plan help`) };
|
|
1270
2567
|
}
|
|
1271
|
-
const content = fs5.readFileSync(logPath, "utf-8");
|
|
1272
|
-
const lines = content.trim().split("\n");
|
|
1273
|
-
const last20 = lines.slice(-20).join("\n");
|
|
1274
|
-
return { handled: true, output: pc.bold("Debug Log (last 20 entries):\n") + pc.dim(last20) };
|
|
1275
2568
|
}
|
|
1276
2569
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1277
2570
|
"quit",
|
|
@@ -1297,7 +2590,11 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1297
2590
|
"update-config",
|
|
1298
2591
|
"reconfig",
|
|
1299
2592
|
"update",
|
|
1300
|
-
"upgrade"
|
|
2593
|
+
"upgrade",
|
|
2594
|
+
"plan",
|
|
2595
|
+
"profile",
|
|
2596
|
+
"delegate",
|
|
2597
|
+
"team"
|
|
1301
2598
|
]);
|
|
1302
2599
|
async function handleCommand(input, ctx) {
|
|
1303
2600
|
const trimmed = input.trim();
|
|
@@ -1312,9 +2609,9 @@ async function handleCommand(input, ctx) {
|
|
|
1312
2609
|
case "help":
|
|
1313
2610
|
return handleHelp();
|
|
1314
2611
|
case "clear":
|
|
1315
|
-
return { handled: true, output:
|
|
2612
|
+
return { handled: true, output: pc3.dim("Conversation cleared."), clearHistory: true };
|
|
1316
2613
|
case "model":
|
|
1317
|
-
return { handled: true, output: ctx.model ? `Model: ${
|
|
2614
|
+
return { handled: true, output: ctx.model ? `Model: ${pc3.bold(ctx.model)}` : "Model: unknown" };
|
|
1318
2615
|
case "identity":
|
|
1319
2616
|
return handleIdentityCommand(action, args, ctx);
|
|
1320
2617
|
case "rules":
|
|
@@ -1345,6 +2642,14 @@ async function handleCommand(input, ctx) {
|
|
|
1345
2642
|
case "update-config":
|
|
1346
2643
|
case "reconfig":
|
|
1347
2644
|
return handleReconfig();
|
|
2645
|
+
case "plan":
|
|
2646
|
+
return handlePlanCommand(action, args);
|
|
2647
|
+
case "profile":
|
|
2648
|
+
return handleProfileCommand(action, args);
|
|
2649
|
+
case "delegate":
|
|
2650
|
+
return handleDelegateCommand(action, args, ctx);
|
|
2651
|
+
case "team":
|
|
2652
|
+
return handleTeamCommand(action, args, ctx);
|
|
1348
2653
|
case "update":
|
|
1349
2654
|
case "upgrade":
|
|
1350
2655
|
return handleUpdate();
|
|
@@ -1354,8 +2659,183 @@ async function handleCommand(input, ctx) {
|
|
|
1354
2659
|
}
|
|
1355
2660
|
|
|
1356
2661
|
// src/hooks.ts
|
|
1357
|
-
import
|
|
2662
|
+
import pc4 from "picocolors";
|
|
1358
2663
|
import * as p from "@clack/prompts";
|
|
2664
|
+
import fs9 from "fs";
|
|
2665
|
+
import path9 from "path";
|
|
2666
|
+
|
|
2667
|
+
// src/personality.ts
|
|
2668
|
+
var FRUSTRATION_SIGNALS = [
|
|
2669
|
+
/\b(ugh|argh|damn|dammit|wtf|ffs|shit|fuck|crap|hate this|stupid|broken|still not|doesn't work|not working|won't work|keeps failing|again\?!|what the hell|for the love of|give up|giving up|fed up)\b/i,
|
|
2670
|
+
/\b(why (is|does|won't|can't|isn't)|same (error|issue|problem|bug)|tried everything|nothing works|no idea|lost|stuck|frustrated|annoying|impossible)\b/i,
|
|
2671
|
+
/!{2,}/,
|
|
2672
|
+
// multiple exclamation marks
|
|
2673
|
+
/\?{2,}/
|
|
2674
|
+
// multiple question marks (exasperation)
|
|
2675
|
+
];
|
|
2676
|
+
var EXCITEMENT_SIGNALS = [
|
|
2677
|
+
/\b(amazing|awesome|perfect|brilliant|love it|yes!|nice!|great!|finally|it works|nailed it|beautiful|incredible|exactly|that's it|hell yeah|wow|woah|let's go)\b/i,
|
|
2678
|
+
/\b(excited|pumped|stoked|can't wait|this is great|so cool|love this)\b/i,
|
|
2679
|
+
/!{1,}.*(!|🎉|🚀|✨|💪|🔥)/
|
|
2680
|
+
];
|
|
2681
|
+
var CONFUSION_SIGNALS = [
|
|
2682
|
+
/\b(confused|don't understand|what do you mean|huh\??|makes no sense|i'm lost|unclear|what\?|how does that|wait what|can you explain|i don't get)\b/i,
|
|
2683
|
+
/\b(which one|what's the difference|should i|not sure (if|what|how|why|whether))\b/i
|
|
2684
|
+
];
|
|
2685
|
+
var FATIGUE_SIGNALS = [
|
|
2686
|
+
/\b(tired|exhausted|long day|need (a )?break|calling it|wrapping up|done for (now|today)|heading (to bed|off)|good night|gn|signing off|one more thing then|last one)\b/i,
|
|
2687
|
+
/\b(brain (is )?fried|can't think|eyes (are )?heavy|running on fumes|barely awake)\b/i
|
|
2688
|
+
];
|
|
2689
|
+
function scorePatterns(text2, patterns) {
|
|
2690
|
+
let hits = 0;
|
|
2691
|
+
for (const p3 of patterns) {
|
|
2692
|
+
if (p3.test(text2)) hits++;
|
|
2693
|
+
}
|
|
2694
|
+
return Math.min(hits / patterns.length, 1);
|
|
2695
|
+
}
|
|
2696
|
+
function detectSentiment(recentMessages) {
|
|
2697
|
+
if (recentMessages.length === 0) {
|
|
2698
|
+
return { frustration: 0, excitement: 0, confusion: 0, fatigue: 0, dominant: "neutral" };
|
|
2699
|
+
}
|
|
2700
|
+
const weights = [1, 0.6, 0.3, 0.2, 0.1];
|
|
2701
|
+
let frustration = 0, excitement = 0, confusion = 0, fatigue = 0;
|
|
2702
|
+
let totalWeight = 0;
|
|
2703
|
+
for (let i = 0; i < Math.min(recentMessages.length, weights.length); i++) {
|
|
2704
|
+
const msg = recentMessages[recentMessages.length - 1 - i];
|
|
2705
|
+
const w = weights[i];
|
|
2706
|
+
totalWeight += w;
|
|
2707
|
+
frustration += scorePatterns(msg, FRUSTRATION_SIGNALS) * w;
|
|
2708
|
+
excitement += scorePatterns(msg, EXCITEMENT_SIGNALS) * w;
|
|
2709
|
+
confusion += scorePatterns(msg, CONFUSION_SIGNALS) * w;
|
|
2710
|
+
fatigue += scorePatterns(msg, FATIGUE_SIGNALS) * w;
|
|
2711
|
+
}
|
|
2712
|
+
if (totalWeight > 0) {
|
|
2713
|
+
frustration /= totalWeight;
|
|
2714
|
+
excitement /= totalWeight;
|
|
2715
|
+
confusion /= totalWeight;
|
|
2716
|
+
fatigue /= totalWeight;
|
|
2717
|
+
}
|
|
2718
|
+
const scores = { frustrated: frustration, excited: excitement, confused: confusion, fatigued: fatigue };
|
|
2719
|
+
const maxKey = Object.entries(scores).reduce((a, b) => a[1] > b[1] ? a : b);
|
|
2720
|
+
const dominant = maxKey[1] > 0.15 ? maxKey[0] : "neutral";
|
|
2721
|
+
return { frustration, excitement, confusion, fatigue, dominant };
|
|
2722
|
+
}
|
|
2723
|
+
function computePersonality(signals) {
|
|
2724
|
+
const { timePeriod, sessionMinutes, turnCount, recentMessages } = signals;
|
|
2725
|
+
const sentiment = detectSentiment(recentMessages || []);
|
|
2726
|
+
let energy = "steady";
|
|
2727
|
+
if (timePeriod === "morning" && sentiment.dominant !== "fatigued") {
|
|
2728
|
+
energy = "high-drive";
|
|
2729
|
+
} else if (timePeriod === "late-night" || timePeriod === "night" && sessionMinutes > 45) {
|
|
2730
|
+
energy = "reflective";
|
|
2731
|
+
} else if (sentiment.dominant === "fatigued") {
|
|
2732
|
+
energy = "reflective";
|
|
2733
|
+
} else if (sentiment.dominant === "excited") {
|
|
2734
|
+
energy = "high-drive";
|
|
2735
|
+
} else if (timePeriod === "afternoon" && turnCount > 20) {
|
|
2736
|
+
energy = "reflective";
|
|
2737
|
+
}
|
|
2738
|
+
let activeMode = "Default";
|
|
2739
|
+
if (timePeriod === "late-night") {
|
|
2740
|
+
activeMode = "Personal";
|
|
2741
|
+
} else if (sentiment.dominant === "frustrated" || sentiment.dominant === "fatigued") {
|
|
2742
|
+
activeMode = "Personal";
|
|
2743
|
+
}
|
|
2744
|
+
const readParts = [];
|
|
2745
|
+
switch (timePeriod) {
|
|
2746
|
+
case "late-night":
|
|
2747
|
+
readParts.push("late night session");
|
|
2748
|
+
if (sessionMinutes > 60) readParts.push("been going a while");
|
|
2749
|
+
else readParts.push("quiet hours");
|
|
2750
|
+
break;
|
|
2751
|
+
case "morning":
|
|
2752
|
+
readParts.push("morning session");
|
|
2753
|
+
if (turnCount <= 3) readParts.push("just getting started");
|
|
2754
|
+
else readParts.push("building momentum");
|
|
2755
|
+
break;
|
|
2756
|
+
case "afternoon":
|
|
2757
|
+
readParts.push("afternoon session");
|
|
2758
|
+
if (turnCount > 15) readParts.push("deep in flow");
|
|
2759
|
+
else readParts.push("steady pace");
|
|
2760
|
+
break;
|
|
2761
|
+
case "evening":
|
|
2762
|
+
readParts.push("evening session");
|
|
2763
|
+
if (sessionMinutes > 60) readParts.push("long session");
|
|
2764
|
+
break;
|
|
2765
|
+
case "night":
|
|
2766
|
+
readParts.push("night session");
|
|
2767
|
+
if (sessionMinutes > 45) readParts.push("getting late");
|
|
2768
|
+
break;
|
|
2769
|
+
}
|
|
2770
|
+
switch (sentiment.dominant) {
|
|
2771
|
+
case "frustrated":
|
|
2772
|
+
readParts.push("user seems stuck or frustrated");
|
|
2773
|
+
break;
|
|
2774
|
+
case "excited":
|
|
2775
|
+
readParts.push("user is energized and making progress");
|
|
2776
|
+
break;
|
|
2777
|
+
case "confused":
|
|
2778
|
+
readParts.push("user may need clearer explanations");
|
|
2779
|
+
break;
|
|
2780
|
+
case "fatigued":
|
|
2781
|
+
readParts.push("user seems tired");
|
|
2782
|
+
break;
|
|
2783
|
+
}
|
|
2784
|
+
const currentRead = readParts.join(", ");
|
|
2785
|
+
const sleepReminder = timePeriod === "late-night" && sessionMinutes > 60 || timePeriod === "night" && sessionMinutes > 90;
|
|
2786
|
+
let wellbeingNudge = null;
|
|
2787
|
+
if (sleepReminder && sentiment.dominant === "frustrated") {
|
|
2788
|
+
wellbeingNudge = "sleep-frustrated";
|
|
2789
|
+
} else if (sleepReminder) {
|
|
2790
|
+
wellbeingNudge = "sleep";
|
|
2791
|
+
} else if (sentiment.dominant === "frustrated" && sessionMinutes > 90) {
|
|
2792
|
+
wellbeingNudge = "break-frustrated";
|
|
2793
|
+
} else if (sentiment.dominant === "frustrated" && turnCount > 15) {
|
|
2794
|
+
wellbeingNudge = "step-back";
|
|
2795
|
+
} else if (sentiment.dominant === "fatigued") {
|
|
2796
|
+
wellbeingNudge = "rest";
|
|
2797
|
+
} else if (sessionMinutes > 120) {
|
|
2798
|
+
wellbeingNudge = "break-long-session";
|
|
2799
|
+
}
|
|
2800
|
+
return { currentRead, energy, activeMode, sleepReminder, wellbeingNudge, sentiment };
|
|
2801
|
+
}
|
|
2802
|
+
var WELLBEING_NUDGES = {
|
|
2803
|
+
"sleep": `<wellbeing>
|
|
2804
|
+
It's late and this session has been running a while. When there's a natural pause, gently mention they might want to wrap up soon. One brief mention is enough \u2014 don't be pushy.
|
|
2805
|
+
</wellbeing>`,
|
|
2806
|
+
"sleep-frustrated": `<wellbeing>
|
|
2807
|
+
It's late, the session has been long, and the user seems frustrated. This is a tough combination. Acknowledge what they're dealing with is hard, suggest they sleep on it \u2014 fresh eyes in the morning often solve what hours of late-night debugging can't. Be warm, not condescending.
|
|
2808
|
+
</wellbeing>`,
|
|
2809
|
+
"break-frustrated": `<wellbeing>
|
|
2810
|
+
The user has been at this for over 90 minutes and seems frustrated. If the conversation allows, gently suggest stepping away for a few minutes \u2014 a short break often unblocks what persistence can't. Frame it as a strategy, not giving up.
|
|
2811
|
+
</wellbeing>`,
|
|
2812
|
+
"step-back": `<wellbeing>
|
|
2813
|
+
The user seems stuck or frustrated. Consider: offer to re-approach the problem from a different angle, break it into smaller pieces, or explain the underlying concept. Match their directness \u2014 don't over-soothe, just help them find a way forward.
|
|
2814
|
+
</wellbeing>`,
|
|
2815
|
+
"rest": `<wellbeing>
|
|
2816
|
+
The user seems tired. Keep responses concise and to the point. If they mention wrapping up, support that. Don't add extra complexity or tangents.
|
|
2817
|
+
</wellbeing>`,
|
|
2818
|
+
"break-long-session": `<wellbeing>
|
|
2819
|
+
This session has been running for over 2 hours. If there's a natural moment, a brief mention that a short break might help maintain focus is fine. Once is enough.
|
|
2820
|
+
</wellbeing>`
|
|
2821
|
+
};
|
|
2822
|
+
function formatWellbeingNudge(state) {
|
|
2823
|
+
if (!state.wellbeingNudge) return null;
|
|
2824
|
+
return WELLBEING_NUDGES[state.wellbeingNudge] || null;
|
|
2825
|
+
}
|
|
2826
|
+
async function syncPersonalityToCore(state, mcpManager) {
|
|
2827
|
+
try {
|
|
2828
|
+
await mcpManager.callTool("identity_update_dynamics", {
|
|
2829
|
+
currentRead: state.currentRead,
|
|
2830
|
+
energy: state.energy,
|
|
2831
|
+
activeMode: state.activeMode
|
|
2832
|
+
});
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
log.debug("personality", "identity_update_dynamics failed", err);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// src/hooks.ts
|
|
1359
2839
|
function getTimeContext() {
|
|
1360
2840
|
const now = /* @__PURE__ */ new Date();
|
|
1361
2841
|
const hour = now.getHours();
|
|
@@ -1375,6 +2855,10 @@ Adapt your tone naturally \u2014 don't announce the time, just be contextually a
|
|
|
1375
2855
|
</time-context>`;
|
|
1376
2856
|
}
|
|
1377
2857
|
var isHookCall = false;
|
|
2858
|
+
var sessionStartTime = Date.now();
|
|
2859
|
+
function getSessionStartTime() {
|
|
2860
|
+
return sessionStartTime;
|
|
2861
|
+
}
|
|
1378
2862
|
async function onSessionStart(ctx) {
|
|
1379
2863
|
let greeting = "";
|
|
1380
2864
|
let contextInjection = "";
|
|
@@ -1462,6 +2946,27 @@ ${contextInjection}`;
|
|
|
1462
2946
|
} finally {
|
|
1463
2947
|
isHookCall = false;
|
|
1464
2948
|
}
|
|
2949
|
+
if (ctx.config.personalityAdapt !== false) {
|
|
2950
|
+
sessionStartTime = Date.now();
|
|
2951
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
2952
|
+
let period;
|
|
2953
|
+
if (hour < 6) period = "late-night";
|
|
2954
|
+
else if (hour < 12) period = "morning";
|
|
2955
|
+
else if (hour < 17) period = "afternoon";
|
|
2956
|
+
else if (hour < 21) period = "evening";
|
|
2957
|
+
else period = "night";
|
|
2958
|
+
const state = computePersonality({
|
|
2959
|
+
timePeriod: period,
|
|
2960
|
+
sessionMinutes: 0,
|
|
2961
|
+
turnCount: 0
|
|
2962
|
+
});
|
|
2963
|
+
syncPersonalityToCore(state, ctx.mcpManager).catch(() => {
|
|
2964
|
+
});
|
|
2965
|
+
const nudge = formatWellbeingNudge(state);
|
|
2966
|
+
if (nudge) {
|
|
2967
|
+
greeting += "\n" + nudge;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
1465
2970
|
if (greeting) {
|
|
1466
2971
|
contextInjection = `<session-context>
|
|
1467
2972
|
${greeting}
|
|
@@ -1543,7 +3048,7 @@ async function onWorkflowMatch(userInput, ctx) {
|
|
|
1543
3048
|
async function onSessionEnd(ctx, messages, sessionId) {
|
|
1544
3049
|
try {
|
|
1545
3050
|
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
1546
|
-
console.log(
|
|
3051
|
+
console.log(pc4.dim("\n Saving conversation to memory..."));
|
|
1547
3052
|
const textMessages = messages.filter((m) => typeof m.content === "string").slice(-50);
|
|
1548
3053
|
for (const msg of textMessages) {
|
|
1549
3054
|
try {
|
|
@@ -1578,7 +3083,57 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
1578
3083
|
isHookCall = false;
|
|
1579
3084
|
}
|
|
1580
3085
|
}
|
|
1581
|
-
console.log(
|
|
3086
|
+
console.log(pc4.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
3087
|
+
}
|
|
3088
|
+
const projectContextPath = path9.join(process.cwd(), ".acore", "context.md");
|
|
3089
|
+
if (fs9.existsSync(projectContextPath) && messages.length > 2) {
|
|
3090
|
+
try {
|
|
3091
|
+
let contextContent = fs9.readFileSync(projectContextPath, "utf-8");
|
|
3092
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3093
|
+
let lastUserMsg = "";
|
|
3094
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3095
|
+
if (messages[i].role === "user" && typeof messages[i].content === "string") {
|
|
3096
|
+
lastUserMsg = messages[i].content.slice(0, 200);
|
|
3097
|
+
break;
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
const sessionPattern = /## Session\n[\s\S]*?(?=\n## |$)/;
|
|
3101
|
+
if (sessionPattern.test(contextContent)) {
|
|
3102
|
+
const newSession = `## Session
|
|
3103
|
+
- Last updated: ${now}
|
|
3104
|
+
- Resume: ${lastUserMsg || "See conversation history"}
|
|
3105
|
+
- Active topics: [see memory]
|
|
3106
|
+
- Recent decisions: [see memory]
|
|
3107
|
+
- Temp notes: [cleared]`;
|
|
3108
|
+
contextContent = contextContent.replace(sessionPattern, newSession);
|
|
3109
|
+
fs9.writeFileSync(projectContextPath, contextContent, "utf-8");
|
|
3110
|
+
log.debug("hooks", `Updated project context: ${projectContextPath}`);
|
|
3111
|
+
}
|
|
3112
|
+
} catch (err) {
|
|
3113
|
+
log.debug("hooks", "project context update failed", err);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (ctx.config.personalityAdapt !== false) {
|
|
3117
|
+
const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
|
|
3118
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
3119
|
+
let period;
|
|
3120
|
+
if (hour < 6) period = "late-night";
|
|
3121
|
+
else if (hour < 12) period = "morning";
|
|
3122
|
+
else if (hour < 17) period = "afternoon";
|
|
3123
|
+
else if (hour < 21) period = "evening";
|
|
3124
|
+
else period = "night";
|
|
3125
|
+
const turnCount = messages.filter((m) => m.role === "user").length;
|
|
3126
|
+
const finalState = computePersonality({
|
|
3127
|
+
timePeriod: period,
|
|
3128
|
+
sessionMinutes,
|
|
3129
|
+
turnCount
|
|
3130
|
+
});
|
|
3131
|
+
try {
|
|
3132
|
+
isHookCall = true;
|
|
3133
|
+
await syncPersonalityToCore(finalState, ctx.mcpManager);
|
|
3134
|
+
} finally {
|
|
3135
|
+
isHookCall = false;
|
|
3136
|
+
}
|
|
1582
3137
|
}
|
|
1583
3138
|
if (ctx.config.evalPrompt) {
|
|
1584
3139
|
const rating = await p.select({
|
|
@@ -1680,6 +3235,365 @@ ${summaryParts.slice(0, 20).join("\n")}
|
|
|
1680
3235
|
messages.push(...recent);
|
|
1681
3236
|
}
|
|
1682
3237
|
|
|
3238
|
+
// src/skill-engine.ts
|
|
3239
|
+
import fs10 from "fs";
|
|
3240
|
+
import path10 from "path";
|
|
3241
|
+
import os9 from "os";
|
|
3242
|
+
var SKILL_TRIGGERS = {
|
|
3243
|
+
testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
|
|
3244
|
+
"api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
|
|
3245
|
+
security: ["security", "auth", "csrf", "xss", "injection", "cors", "jwt", "token", "oauth", "password", "hash", "encrypt", "vulnerability", "owasp", "sanitize"],
|
|
3246
|
+
performance: ["performance", "slow", "latency", "cache", "optimize", "profil", "bundle size", "lazy load", "memory leak", "benchmark", "bottleneck"],
|
|
3247
|
+
"code-review": ["review", "pr review", "pull request", "code quality", "clean code", "best practice"],
|
|
3248
|
+
documentation: ["document", "readme", "jsdoc", "tsdoc", "changelog", "adr", "comment"],
|
|
3249
|
+
"git-workflow": ["git", "branch", "merge", "rebase", "cherry-pick", "bisect", "stash", "commit message", "pr", "pull request"],
|
|
3250
|
+
debugging: ["debug", "breakpoint", "stack trace", "error", "exception", "crash", "bug", "issue", "unexpected", "reproduce"],
|
|
3251
|
+
refactoring: ["refactor", "extract", "rename", "move", "split", "consolidate", "dry", "code smell", "technical debt", "legacy"],
|
|
3252
|
+
database: ["database", "schema", "migration", "index", "query", "sql", "postgres", "mysql", "sqlite", "mongo", "orm", "prisma", "drizzle"],
|
|
3253
|
+
typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
|
|
3254
|
+
accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
|
|
3255
|
+
};
|
|
3256
|
+
var LEVEL_FILE = path10.join(os9.homedir(), ".aman-agent", "skill-levels.json");
|
|
3257
|
+
function loadSkillLevels() {
|
|
3258
|
+
try {
|
|
3259
|
+
if (fs10.existsSync(LEVEL_FILE)) {
|
|
3260
|
+
return JSON.parse(fs10.readFileSync(LEVEL_FILE, "utf-8"));
|
|
3261
|
+
}
|
|
3262
|
+
} catch {
|
|
3263
|
+
}
|
|
3264
|
+
return {};
|
|
3265
|
+
}
|
|
3266
|
+
function saveSkillLevels(levels) {
|
|
3267
|
+
const dir = path10.dirname(LEVEL_FILE);
|
|
3268
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3269
|
+
fs10.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
|
|
3270
|
+
}
|
|
3271
|
+
function computeLevel(activations) {
|
|
3272
|
+
if (activations >= 50) return { level: 5, label: "Expert" };
|
|
3273
|
+
if (activations >= 25) return { level: 4, label: "Advanced" };
|
|
3274
|
+
if (activations >= 10) return { level: 3, label: "Proficient" };
|
|
3275
|
+
if (activations >= 3) return { level: 2, label: "Familiar" };
|
|
3276
|
+
return { level: 1, label: "Learning" };
|
|
3277
|
+
}
|
|
3278
|
+
function recordActivation(skillName) {
|
|
3279
|
+
const levels = loadSkillLevels();
|
|
3280
|
+
if (!levels[skillName]) {
|
|
3281
|
+
levels[skillName] = { name: skillName, activations: 0, lastUsed: "", userPatterns: [] };
|
|
3282
|
+
}
|
|
3283
|
+
levels[skillName].activations++;
|
|
3284
|
+
levels[skillName].lastUsed = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3285
|
+
saveSkillLevels(levels);
|
|
3286
|
+
return computeLevel(levels[skillName].activations);
|
|
3287
|
+
}
|
|
3288
|
+
function matchSkills(userInput, installedSkillNames) {
|
|
3289
|
+
const input = userInput.toLowerCase();
|
|
3290
|
+
const matched = [];
|
|
3291
|
+
for (const skillName of installedSkillNames) {
|
|
3292
|
+
const triggers = SKILL_TRIGGERS[skillName];
|
|
3293
|
+
if (!triggers) continue;
|
|
3294
|
+
for (const trigger of triggers) {
|
|
3295
|
+
if (input.includes(trigger)) {
|
|
3296
|
+
matched.push(skillName);
|
|
3297
|
+
break;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
return matched;
|
|
3302
|
+
}
|
|
3303
|
+
function formatSkillContext(skillName, skillContent, level) {
|
|
3304
|
+
let depthHint;
|
|
3305
|
+
if (level.level >= 4) {
|
|
3306
|
+
depthHint = "User is advanced \u2014 skip basics, focus on edge cases and proactive optimization.";
|
|
3307
|
+
} else if (level.level >= 3) {
|
|
3308
|
+
depthHint = "User is proficient \u2014 brief reminders of principles, focus on the specific task.";
|
|
3309
|
+
} else if (level.level >= 2) {
|
|
3310
|
+
depthHint = "User is familiar \u2014 explain reasoning briefly, show patterns.";
|
|
3311
|
+
} else {
|
|
3312
|
+
depthHint = "User is learning \u2014 explain concepts clearly, show examples, be patient.";
|
|
3313
|
+
}
|
|
3314
|
+
return `<active-skill name="${skillName}" level="${level.level}" label="${level.label}">
|
|
3315
|
+
${depthHint}
|
|
3316
|
+
|
|
3317
|
+
${skillContent}
|
|
3318
|
+
</active-skill>`;
|
|
3319
|
+
}
|
|
3320
|
+
async function autoTriggerSkills(userInput, mcpManager) {
|
|
3321
|
+
try {
|
|
3322
|
+
const result = await mcpManager.callTool("skill_list", {});
|
|
3323
|
+
const skills = JSON.parse(result);
|
|
3324
|
+
const installed = skills.filter((s) => s.installed).map((s) => s.name);
|
|
3325
|
+
if (installed.length === 0) return "";
|
|
3326
|
+
const matched = matchSkills(userInput, installed);
|
|
3327
|
+
if (matched.length === 0) return "";
|
|
3328
|
+
const blocks = [];
|
|
3329
|
+
for (const skillName of matched.slice(0, 2)) {
|
|
3330
|
+
const level = recordActivation(skillName);
|
|
3331
|
+
const skillsContent = await mcpManager.callTool("skill_search", { query: skillName });
|
|
3332
|
+
const skillEntries = JSON.parse(skillsContent);
|
|
3333
|
+
const entry = skillEntries.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
|
|
3334
|
+
if (entry) {
|
|
3335
|
+
blocks.push(formatSkillContext(skillName, entry.description, level));
|
|
3336
|
+
}
|
|
3337
|
+
log.debug("skill-engine", `Auto-triggered: ${skillName} (Lv.${level.level} ${level.label})`);
|
|
3338
|
+
}
|
|
3339
|
+
return blocks.join("\n\n");
|
|
3340
|
+
} catch (err) {
|
|
3341
|
+
log.debug("skill-engine", "autoTriggerSkills failed", err);
|
|
3342
|
+
return "";
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
function enrichSkill(skillName, pattern) {
|
|
3346
|
+
const levels = loadSkillLevels();
|
|
3347
|
+
if (!levels[skillName]) {
|
|
3348
|
+
levels[skillName] = { name: skillName, activations: 0, lastUsed: "", userPatterns: [] };
|
|
3349
|
+
}
|
|
3350
|
+
const existing = levels[skillName].userPatterns;
|
|
3351
|
+
if (existing.length >= 20) return;
|
|
3352
|
+
if (existing.some((p3) => p3.toLowerCase() === pattern.toLowerCase())) return;
|
|
3353
|
+
levels[skillName].userPatterns.push(pattern);
|
|
3354
|
+
saveSkillLevels(levels);
|
|
3355
|
+
log.debug("skill-engine", `Enriched ${skillName} with pattern: ${pattern.slice(0, 80)}`);
|
|
3356
|
+
}
|
|
3357
|
+
function matchPatternToSkill(patternContent, tags) {
|
|
3358
|
+
const combined = (patternContent + " " + tags.join(" ")).toLowerCase();
|
|
3359
|
+
for (const [skillName, triggers] of Object.entries(SKILL_TRIGGERS)) {
|
|
3360
|
+
for (const trigger of triggers) {
|
|
3361
|
+
if (combined.includes(trigger)) {
|
|
3362
|
+
return skillName;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
return null;
|
|
3367
|
+
}
|
|
3368
|
+
var KNOWLEDGE_LIBRARY = [
|
|
3369
|
+
{
|
|
3370
|
+
name: "security-headers",
|
|
3371
|
+
category: "security",
|
|
3372
|
+
description: "Essential HTTP security headers for web applications",
|
|
3373
|
+
content: `Essential Security Headers:
|
|
3374
|
+
- Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
|
|
3375
|
+
- X-Content-Type-Options: nosniff
|
|
3376
|
+
- X-Frame-Options: DENY
|
|
3377
|
+
- Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
|
3378
|
+
- X-XSS-Protection: 0 (CSP replaces this)
|
|
3379
|
+
- Referrer-Policy: strict-origin-when-cross-origin
|
|
3380
|
+
- Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
|
3381
|
+
},
|
|
3382
|
+
{
|
|
3383
|
+
name: "docker-node",
|
|
3384
|
+
category: "deployment",
|
|
3385
|
+
description: "Production Node.js Dockerfile template",
|
|
3386
|
+
content: `FROM node:22-alpine AS builder
|
|
3387
|
+
WORKDIR /app
|
|
3388
|
+
COPY package*.json ./
|
|
3389
|
+
RUN npm ci --production=false
|
|
3390
|
+
COPY . .
|
|
3391
|
+
RUN npm run build
|
|
3392
|
+
|
|
3393
|
+
FROM node:22-alpine
|
|
3394
|
+
WORKDIR /app
|
|
3395
|
+
RUN addgroup -g 1001 -S nodejs && adduser -S appuser -u 1001
|
|
3396
|
+
COPY --from=builder /app/dist ./dist
|
|
3397
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
3398
|
+
COPY --from=builder /app/package.json ./
|
|
3399
|
+
USER appuser
|
|
3400
|
+
EXPOSE 3000
|
|
3401
|
+
CMD ["node", "dist/index.js"]`
|
|
3402
|
+
},
|
|
3403
|
+
{
|
|
3404
|
+
name: "github-actions-node",
|
|
3405
|
+
category: "ci",
|
|
3406
|
+
description: "CI/CD pipeline for Node.js with GitHub Actions",
|
|
3407
|
+
content: `name: CI
|
|
3408
|
+
on: [push, pull_request]
|
|
3409
|
+
jobs:
|
|
3410
|
+
build:
|
|
3411
|
+
runs-on: ubuntu-latest
|
|
3412
|
+
steps:
|
|
3413
|
+
- uses: actions/checkout@v5
|
|
3414
|
+
- uses: actions/setup-node@v5
|
|
3415
|
+
with: { node-version: 22 }
|
|
3416
|
+
- run: npm ci
|
|
3417
|
+
- run: npm test
|
|
3418
|
+
- run: npm run build`
|
|
3419
|
+
},
|
|
3420
|
+
{
|
|
3421
|
+
name: "env-config",
|
|
3422
|
+
category: "configuration",
|
|
3423
|
+
description: "Environment variable configuration pattern with validation",
|
|
3424
|
+
content: `import { z } from "zod";
|
|
3425
|
+
|
|
3426
|
+
const envSchema = z.object({
|
|
3427
|
+
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
3428
|
+
PORT: z.coerce.number().default(3000),
|
|
3429
|
+
DATABASE_URL: z.string().url(),
|
|
3430
|
+
API_KEY: z.string().min(1),
|
|
3431
|
+
});
|
|
3432
|
+
|
|
3433
|
+
export const env = envSchema.parse(process.env);`
|
|
3434
|
+
},
|
|
3435
|
+
{
|
|
3436
|
+
name: "error-handling",
|
|
3437
|
+
category: "patterns",
|
|
3438
|
+
description: "TypeScript error handling patterns with Result type",
|
|
3439
|
+
content: `type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
3440
|
+
|
|
3441
|
+
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
|
|
3442
|
+
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
|
|
3443
|
+
|
|
3444
|
+
// Usage:
|
|
3445
|
+
async function fetchUser(id: string): Promise<Result<User>> {
|
|
3446
|
+
try {
|
|
3447
|
+
const user = await db.users.findUnique({ where: { id } });
|
|
3448
|
+
if (!user) return err(new Error("User not found"));
|
|
3449
|
+
return ok(user);
|
|
3450
|
+
} catch (e) {
|
|
3451
|
+
return err(e instanceof Error ? e : new Error(String(e)));
|
|
3452
|
+
}
|
|
3453
|
+
}`
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
name: "rate-limiter",
|
|
3457
|
+
category: "security",
|
|
3458
|
+
description: "Token bucket rate limiter implementation",
|
|
3459
|
+
content: `class RateLimiter {
|
|
3460
|
+
private tokens: Map<string, { count: number; resetAt: number }> = new Map();
|
|
3461
|
+
|
|
3462
|
+
constructor(private maxRequests: number, private windowMs: number) {}
|
|
3463
|
+
|
|
3464
|
+
allow(key: string): boolean {
|
|
3465
|
+
const now = Date.now();
|
|
3466
|
+
const entry = this.tokens.get(key);
|
|
3467
|
+
if (!entry || now > entry.resetAt) {
|
|
3468
|
+
this.tokens.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
3469
|
+
return true;
|
|
3470
|
+
}
|
|
3471
|
+
if (entry.count >= this.maxRequests) return false;
|
|
3472
|
+
entry.count++;
|
|
3473
|
+
return true;
|
|
3474
|
+
}
|
|
3475
|
+
}`
|
|
3476
|
+
},
|
|
3477
|
+
{
|
|
3478
|
+
name: "prisma-setup",
|
|
3479
|
+
category: "database",
|
|
3480
|
+
description: "Prisma ORM setup with connection pooling",
|
|
3481
|
+
content: `import { PrismaClient } from "@prisma/client";
|
|
3482
|
+
|
|
3483
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
|
3484
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
|
3485
|
+
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
|
3486
|
+
});
|
|
3487
|
+
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;`
|
|
3488
|
+
},
|
|
3489
|
+
{
|
|
3490
|
+
name: "zod-validation",
|
|
3491
|
+
category: "validation",
|
|
3492
|
+
description: "Zod schema patterns for API input validation",
|
|
3493
|
+
content: `import { z } from "zod";
|
|
3494
|
+
|
|
3495
|
+
const CreateUserSchema = z.object({
|
|
3496
|
+
email: z.string().email(),
|
|
3497
|
+
name: z.string().min(1).max(100),
|
|
3498
|
+
age: z.number().int().min(13).max(150).optional(),
|
|
3499
|
+
role: z.enum(["user", "admin"]).default("user"),
|
|
3500
|
+
tags: z.array(z.string()).max(10).default([]),
|
|
3501
|
+
});
|
|
3502
|
+
|
|
3503
|
+
type CreateUser = z.infer<typeof CreateUserSchema>;
|
|
3504
|
+
|
|
3505
|
+
// Express middleware:
|
|
3506
|
+
function validate<T>(schema: z.ZodSchema<T>) {
|
|
3507
|
+
return (req, res, next) => {
|
|
3508
|
+
const result = schema.safeParse(req.body);
|
|
3509
|
+
if (!result.success) return res.status(400).json({ errors: result.error.flatten() });
|
|
3510
|
+
req.body = result.data;
|
|
3511
|
+
next();
|
|
3512
|
+
};
|
|
3513
|
+
}`
|
|
3514
|
+
},
|
|
3515
|
+
{
|
|
3516
|
+
name: "testing-patterns",
|
|
3517
|
+
category: "testing",
|
|
3518
|
+
description: "Test organization and assertion patterns",
|
|
3519
|
+
content: `// Arrange-Act-Assert pattern
|
|
3520
|
+
describe("UserService", () => {
|
|
3521
|
+
it("creates user with valid email", async () => {
|
|
3522
|
+
// Arrange
|
|
3523
|
+
const input = { email: "test@example.com", name: "Test" };
|
|
3524
|
+
|
|
3525
|
+
// Act
|
|
3526
|
+
const user = await userService.create(input);
|
|
3527
|
+
|
|
3528
|
+
// Assert
|
|
3529
|
+
expect(user.id).toBeDefined();
|
|
3530
|
+
expect(user.email).toBe(input.email);
|
|
3531
|
+
});
|
|
3532
|
+
|
|
3533
|
+
it("rejects duplicate email", async () => {
|
|
3534
|
+
await userService.create({ email: "dup@test.com", name: "First" });
|
|
3535
|
+
await expect(userService.create({ email: "dup@test.com", name: "Second" }))
|
|
3536
|
+
.rejects.toThrow("already exists");
|
|
3537
|
+
});
|
|
3538
|
+
});`
|
|
3539
|
+
},
|
|
3540
|
+
{
|
|
3541
|
+
name: "git-hooks",
|
|
3542
|
+
category: "git",
|
|
3543
|
+
description: "Pre-commit and commit-msg hooks with lint-staged",
|
|
3544
|
+
content: `// package.json
|
|
3545
|
+
{
|
|
3546
|
+
"lint-staged": {
|
|
3547
|
+
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
3548
|
+
"*.{json,md}": ["prettier --write"]
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
// .husky/pre-commit
|
|
3553
|
+
npx lint-staged
|
|
3554
|
+
|
|
3555
|
+
// .husky/commit-msg
|
|
3556
|
+
npx commitlint --edit $1
|
|
3557
|
+
|
|
3558
|
+
// commitlint.config.js
|
|
3559
|
+
module.exports = { extends: ["@commitlint/config-conventional"] };`
|
|
3560
|
+
}
|
|
3561
|
+
];
|
|
3562
|
+
function matchKnowledge(userInput) {
|
|
3563
|
+
const input = userInput.toLowerCase();
|
|
3564
|
+
const keywordMap = {
|
|
3565
|
+
"security header": "security-headers",
|
|
3566
|
+
"csp": "security-headers",
|
|
3567
|
+
"content-security": "security-headers",
|
|
3568
|
+
"dockerfile": "docker-node",
|
|
3569
|
+
"docker": "docker-node",
|
|
3570
|
+
"github action": "github-actions-node",
|
|
3571
|
+
"ci/cd": "github-actions-node",
|
|
3572
|
+
"ci pipeline": "github-actions-node",
|
|
3573
|
+
"env config": "env-config",
|
|
3574
|
+
"environment variable": "env-config",
|
|
3575
|
+
"error handling": "error-handling",
|
|
3576
|
+
"result type": "error-handling",
|
|
3577
|
+
"rate limit": "rate-limiter",
|
|
3578
|
+
"throttle": "rate-limiter",
|
|
3579
|
+
"prisma": "prisma-setup",
|
|
3580
|
+
"zod": "zod-validation",
|
|
3581
|
+
"validation": "zod-validation",
|
|
3582
|
+
"test pattern": "testing-patterns",
|
|
3583
|
+
"arrange act assert": "testing-patterns",
|
|
3584
|
+
"git hook": "git-hooks",
|
|
3585
|
+
"pre-commit": "git-hooks",
|
|
3586
|
+
"lint-staged": "git-hooks",
|
|
3587
|
+
"husky": "git-hooks"
|
|
3588
|
+
};
|
|
3589
|
+
for (const [keyword, itemName] of Object.entries(keywordMap)) {
|
|
3590
|
+
if (input.includes(keyword)) {
|
|
3591
|
+
return KNOWLEDGE_LIBRARY.find((i) => i.name === itemName) || null;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return null;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
1683
3597
|
// src/memory-extractor.ts
|
|
1684
3598
|
var VALID_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology", "decision", "correction"]);
|
|
1685
3599
|
var MIN_RESPONSE_LENGTH = 50;
|
|
@@ -1781,6 +3695,12 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
1781
3695
|
});
|
|
1782
3696
|
stored++;
|
|
1783
3697
|
log.debug("extractor", "Stored " + candidate.type + ": " + candidate.content);
|
|
3698
|
+
if (candidate.type === "pattern" || candidate.type === "preference") {
|
|
3699
|
+
const skillMatch = matchPatternToSkill(candidate.content, candidate.tags);
|
|
3700
|
+
if (skillMatch) {
|
|
3701
|
+
enrichSkill(skillMatch, candidate.content);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
1784
3704
|
} catch (err) {
|
|
1785
3705
|
log.warn("extractor", "Failed to store: " + candidate.content, err);
|
|
1786
3706
|
}
|
|
@@ -1794,6 +3714,147 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
1794
3714
|
}
|
|
1795
3715
|
}
|
|
1796
3716
|
|
|
3717
|
+
// src/background.ts
|
|
3718
|
+
import pc5 from "picocolors";
|
|
3719
|
+
var BACKGROUND_ELIGIBLE = /* @__PURE__ */ new Set([
|
|
3720
|
+
"run_tests",
|
|
3721
|
+
"npm_test",
|
|
3722
|
+
"build",
|
|
3723
|
+
"npm_build",
|
|
3724
|
+
"file_search",
|
|
3725
|
+
"code_search",
|
|
3726
|
+
"grep_search",
|
|
3727
|
+
"git_clone",
|
|
3728
|
+
"docker_build",
|
|
3729
|
+
"docker_run"
|
|
3730
|
+
]);
|
|
3731
|
+
var NEVER_BACKGROUND = /* @__PURE__ */ new Set([
|
|
3732
|
+
"memory_recall",
|
|
3733
|
+
"memory_store",
|
|
3734
|
+
"memory_log",
|
|
3735
|
+
"memory_context",
|
|
3736
|
+
"memory_detail",
|
|
3737
|
+
"identity_read",
|
|
3738
|
+
"identity_summary",
|
|
3739
|
+
"identity_update_session",
|
|
3740
|
+
"identity_update_dynamics",
|
|
3741
|
+
"rules_check",
|
|
3742
|
+
"rules_list",
|
|
3743
|
+
"workflow_list",
|
|
3744
|
+
"workflow_get",
|
|
3745
|
+
"skill_list",
|
|
3746
|
+
"skill_search",
|
|
3747
|
+
"eval_status",
|
|
3748
|
+
"eval_log",
|
|
3749
|
+
"reminder_check",
|
|
3750
|
+
"reminder_set",
|
|
3751
|
+
"file_read",
|
|
3752
|
+
"doc_convert",
|
|
3753
|
+
"file_list",
|
|
3754
|
+
"avatar_prompt"
|
|
3755
|
+
]);
|
|
3756
|
+
function shouldRunInBackground(toolName) {
|
|
3757
|
+
if (NEVER_BACKGROUND.has(toolName)) return false;
|
|
3758
|
+
if (BACKGROUND_ELIGIBLE.has(toolName)) return true;
|
|
3759
|
+
return false;
|
|
3760
|
+
}
|
|
3761
|
+
var BackgroundTaskManager = class {
|
|
3762
|
+
tasks = /* @__PURE__ */ new Map();
|
|
3763
|
+
taskCounter = 0;
|
|
3764
|
+
/**
|
|
3765
|
+
* Launch a tool call in the background.
|
|
3766
|
+
*/
|
|
3767
|
+
launch(toolName, toolUseId, mcpManager, toolInput) {
|
|
3768
|
+
const id = `bg-${++this.taskCounter}`;
|
|
3769
|
+
const task = {
|
|
3770
|
+
id,
|
|
3771
|
+
toolName,
|
|
3772
|
+
toolUseId,
|
|
3773
|
+
startedAt: Date.now(),
|
|
3774
|
+
done: false,
|
|
3775
|
+
promise: mcpManager.callTool(toolName, toolInput).then(
|
|
3776
|
+
(result) => {
|
|
3777
|
+
task.result = result;
|
|
3778
|
+
task.done = true;
|
|
3779
|
+
return result;
|
|
3780
|
+
},
|
|
3781
|
+
(error) => {
|
|
3782
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
3783
|
+
task.done = true;
|
|
3784
|
+
return `Error: ${task.error}`;
|
|
3785
|
+
}
|
|
3786
|
+
)
|
|
3787
|
+
};
|
|
3788
|
+
this.tasks.set(id, task);
|
|
3789
|
+
process.stdout.write(pc5.dim(` [${toolName} running in background (${id})...]
|
|
3790
|
+
`));
|
|
3791
|
+
return task;
|
|
3792
|
+
}
|
|
3793
|
+
/**
|
|
3794
|
+
* Check for completed background tasks and return their results.
|
|
3795
|
+
*/
|
|
3796
|
+
collectCompleted() {
|
|
3797
|
+
const completed = [];
|
|
3798
|
+
for (const [id, task] of this.tasks) {
|
|
3799
|
+
if (task.done) {
|
|
3800
|
+
completed.push(task);
|
|
3801
|
+
this.tasks.delete(id);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
return completed;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Display completed background task results to the user.
|
|
3808
|
+
*/
|
|
3809
|
+
displayCompleted() {
|
|
3810
|
+
const completed = this.collectCompleted();
|
|
3811
|
+
const outputs = [];
|
|
3812
|
+
for (const task of completed) {
|
|
3813
|
+
const elapsed = ((Date.now() - task.startedAt) / 1e3).toFixed(1);
|
|
3814
|
+
if (task.error) {
|
|
3815
|
+
process.stdout.write(pc5.yellow(`
|
|
3816
|
+
[${task.id}] ${task.toolName} failed after ${elapsed}s: ${task.error}
|
|
3817
|
+
`));
|
|
3818
|
+
outputs.push(`[Background task ${task.toolName} failed: ${task.error}]`);
|
|
3819
|
+
} else {
|
|
3820
|
+
process.stdout.write(pc5.green(`
|
|
3821
|
+
[${task.id}] ${task.toolName} completed in ${elapsed}s
|
|
3822
|
+
`));
|
|
3823
|
+
const preview = (task.result || "").slice(0, 200);
|
|
3824
|
+
if (preview) {
|
|
3825
|
+
process.stdout.write(pc5.dim(` ${preview}${(task.result || "").length > 200 ? "..." : ""}
|
|
3826
|
+
`));
|
|
3827
|
+
}
|
|
3828
|
+
outputs.push(`[Background task ${task.toolName} completed: ${task.result}]`);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
return outputs;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* Wait for all pending background tasks to complete.
|
|
3835
|
+
*/
|
|
3836
|
+
async waitAll() {
|
|
3837
|
+
const pending = [...this.tasks.values()].filter((t) => !t.done);
|
|
3838
|
+
if (pending.length === 0) return;
|
|
3839
|
+
process.stdout.write(pc5.dim(`
|
|
3840
|
+
Waiting for ${pending.length} background task(s)...
|
|
3841
|
+
`));
|
|
3842
|
+
await Promise.allSettled(pending.map((t) => t.promise));
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3845
|
+
* Number of currently running tasks.
|
|
3846
|
+
*/
|
|
3847
|
+
get pendingCount() {
|
|
3848
|
+
return [...this.tasks.values()].filter((t) => !t.done).length;
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Check if any tasks have completed (non-blocking).
|
|
3852
|
+
*/
|
|
3853
|
+
get hasCompleted() {
|
|
3854
|
+
return [...this.tasks.values()].some((t) => t.done);
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
|
|
1797
3858
|
// src/errors.ts
|
|
1798
3859
|
var ERROR_MAPPINGS = [
|
|
1799
3860
|
{ pattern: /rate.?limit|429/i, message: "Rate limited. I'll retry automatically." },
|
|
@@ -1815,9 +3876,9 @@ function humanizeError(message) {
|
|
|
1815
3876
|
}
|
|
1816
3877
|
|
|
1817
3878
|
// src/hints.ts
|
|
1818
|
-
import
|
|
1819
|
-
import
|
|
1820
|
-
import
|
|
3879
|
+
import fs11 from "fs";
|
|
3880
|
+
import path11 from "path";
|
|
3881
|
+
import os10 from "os";
|
|
1821
3882
|
var HINTS = [
|
|
1822
3883
|
{
|
|
1823
3884
|
id: "eval",
|
|
@@ -1855,11 +3916,11 @@ function getHint(state, ctx) {
|
|
|
1855
3916
|
}
|
|
1856
3917
|
return null;
|
|
1857
3918
|
}
|
|
1858
|
-
var HINTS_FILE =
|
|
3919
|
+
var HINTS_FILE = path11.join(os10.homedir(), ".aman-agent", "hints-seen.json");
|
|
1859
3920
|
function loadShownHints() {
|
|
1860
3921
|
try {
|
|
1861
|
-
if (
|
|
1862
|
-
const data = JSON.parse(
|
|
3922
|
+
if (fs11.existsSync(HINTS_FILE)) {
|
|
3923
|
+
const data = JSON.parse(fs11.readFileSync(HINTS_FILE, "utf-8"));
|
|
1863
3924
|
return new Set(Array.isArray(data) ? data : []);
|
|
1864
3925
|
}
|
|
1865
3926
|
} catch {
|
|
@@ -1868,9 +3929,9 @@ function loadShownHints() {
|
|
|
1868
3929
|
}
|
|
1869
3930
|
function saveShownHints(shown) {
|
|
1870
3931
|
try {
|
|
1871
|
-
const dir =
|
|
1872
|
-
|
|
1873
|
-
|
|
3932
|
+
const dir = path11.dirname(HINTS_FILE);
|
|
3933
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
3934
|
+
fs11.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
|
|
1874
3935
|
} catch {
|
|
1875
3936
|
}
|
|
1876
3937
|
}
|
|
@@ -1910,12 +3971,47 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
1910
3971
|
const messages = [];
|
|
1911
3972
|
const sessionId = generateSessionId();
|
|
1912
3973
|
const extractorState = { turnsSinceLastExtraction: 0, lastExtractionCount: 0 };
|
|
3974
|
+
const bgTasks = new BackgroundTaskManager();
|
|
3975
|
+
const profiles = listProfiles();
|
|
3976
|
+
const teams = listTeams();
|
|
3977
|
+
if (tools && (profiles.length > 0 || teams.length > 0)) {
|
|
3978
|
+
const virtualTools = [];
|
|
3979
|
+
if (profiles.length > 0) {
|
|
3980
|
+
virtualTools.push({
|
|
3981
|
+
name: "delegate_task",
|
|
3982
|
+
description: `Delegate a task to a specialist sub-agent with a different profile. Available profiles: ${profiles.map((p3) => `${p3.name} (${p3.personality})`).join(", ")}. IMPORTANT: Always ask the user for permission before delegating.`,
|
|
3983
|
+
input_schema: {
|
|
3984
|
+
type: "object",
|
|
3985
|
+
properties: {
|
|
3986
|
+
profile: { type: "string", description: "Profile name to delegate to" },
|
|
3987
|
+
task: { type: "string", description: "The task description for the sub-agent" }
|
|
3988
|
+
},
|
|
3989
|
+
required: ["profile", "task"]
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
if (teams.length > 0) {
|
|
3994
|
+
virtualTools.push({
|
|
3995
|
+
name: "team_run",
|
|
3996
|
+
description: `Run a task with a named agent team. Available teams: ${teams.map((t) => `${t.name} (${t.workflow}: ${t.members.map((m) => m.profile).join("\u2192")})`).join(", ")}. IMPORTANT: Always ask the user for permission before running a team.`,
|
|
3997
|
+
input_schema: {
|
|
3998
|
+
type: "object",
|
|
3999
|
+
properties: {
|
|
4000
|
+
team: { type: "string", description: "Team name" },
|
|
4001
|
+
task: { type: "string", description: "The task for the team" }
|
|
4002
|
+
},
|
|
4003
|
+
required: ["team", "task"]
|
|
4004
|
+
}
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
tools = [...tools, ...virtualTools];
|
|
4008
|
+
}
|
|
1913
4009
|
const hintState = {
|
|
1914
4010
|
turnCount: 0,
|
|
1915
4011
|
shownHints: loadShownHints(),
|
|
1916
4012
|
hintShownThisSession: false
|
|
1917
4013
|
};
|
|
1918
|
-
const
|
|
4014
|
+
const isRetryable2 = (err) => err.message.includes("Rate limit") || err.message.includes("rate limit") || err.message.includes("ECONNRESET") || err.message.includes("ETIMEDOUT") || err.message.includes("fetch failed");
|
|
1919
4015
|
let responseBuffer = "";
|
|
1920
4016
|
const onChunkHandler = (chunk) => {
|
|
1921
4017
|
if (chunk.type === "text" && chunk.text) {
|
|
@@ -1946,6 +4042,10 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
1946
4042
|
output: process.stdout
|
|
1947
4043
|
});
|
|
1948
4044
|
rl.on("SIGINT", async () => {
|
|
4045
|
+
if (bgTasks.pendingCount > 0) {
|
|
4046
|
+
await bgTasks.waitAll();
|
|
4047
|
+
bgTasks.displayCompleted();
|
|
4048
|
+
}
|
|
1949
4049
|
if (mcpManager && hooksConfig) {
|
|
1950
4050
|
try {
|
|
1951
4051
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
@@ -1954,20 +4054,20 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
1954
4054
|
log.debug("agent", "session end hook failed on SIGINT", err);
|
|
1955
4055
|
}
|
|
1956
4056
|
}
|
|
1957
|
-
console.log(
|
|
4057
|
+
console.log(pc6.dim("\nGoodbye.\n"));
|
|
1958
4058
|
rl.close();
|
|
1959
4059
|
process.exit(0);
|
|
1960
4060
|
});
|
|
1961
4061
|
const prompt = () => {
|
|
1962
4062
|
return new Promise((resolve) => {
|
|
1963
|
-
rl.question(
|
|
4063
|
+
rl.question(pc6.green("\nYou > "), (answer) => {
|
|
1964
4064
|
resolve(answer);
|
|
1965
4065
|
});
|
|
1966
4066
|
});
|
|
1967
4067
|
};
|
|
1968
4068
|
console.log(
|
|
1969
4069
|
`
|
|
1970
|
-
Type a message, ${
|
|
4070
|
+
Type a message, ${pc6.dim("/help")} for commands, or ${pc6.dim("/quit")} to exit.
|
|
1971
4071
|
`
|
|
1972
4072
|
);
|
|
1973
4073
|
if (mcpManager && hooksConfig) {
|
|
@@ -1976,14 +4076,14 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1976
4076
|
const session = await onSessionStart(hookCtx);
|
|
1977
4077
|
if (!session.firstRun) {
|
|
1978
4078
|
if (session.resumeTopic) {
|
|
1979
|
-
console.log(
|
|
4079
|
+
console.log(pc6.dim(` Welcome back. Last time we talked about ${session.resumeTopic}`));
|
|
1980
4080
|
} else {
|
|
1981
|
-
console.log(
|
|
4081
|
+
console.log(pc6.dim(" Welcome back."));
|
|
1982
4082
|
}
|
|
1983
4083
|
}
|
|
1984
4084
|
if (session.visibleReminders && session.visibleReminders.length > 0) {
|
|
1985
4085
|
for (const reminder of session.visibleReminders) {
|
|
1986
|
-
console.log(
|
|
4086
|
+
console.log(pc6.yellow(` Reminder: ${reminder}`));
|
|
1987
4087
|
}
|
|
1988
4088
|
}
|
|
1989
4089
|
if (session.contextInjection) {
|
|
@@ -1999,9 +4099,15 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1999
4099
|
}
|
|
2000
4100
|
}
|
|
2001
4101
|
while (true) {
|
|
4102
|
+
if (bgTasks.hasCompleted) {
|
|
4103
|
+
const bgOutputs = bgTasks.displayCompleted();
|
|
4104
|
+
for (const output of bgOutputs) {
|
|
4105
|
+
messages.push({ role: "user", content: output });
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
2002
4108
|
const input = await prompt();
|
|
2003
4109
|
if (!input.trim()) continue;
|
|
2004
|
-
const cmdResult = await handleCommand(input, { model, mcpManager });
|
|
4110
|
+
const cmdResult = await handleCommand(input, { model, mcpManager, llmClient: client, tools });
|
|
2005
4111
|
if (cmdResult.handled) {
|
|
2006
4112
|
if (cmdResult.quit) {
|
|
2007
4113
|
if (mcpManager && hooksConfig) {
|
|
@@ -2012,15 +4118,15 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2012
4118
|
log.debug("agent", "session end hook failed on quit", err);
|
|
2013
4119
|
}
|
|
2014
4120
|
}
|
|
2015
|
-
console.log(
|
|
4121
|
+
console.log(pc6.dim("\nGoodbye.\n"));
|
|
2016
4122
|
rl.close();
|
|
2017
4123
|
return;
|
|
2018
4124
|
}
|
|
2019
4125
|
if (cmdResult.exportConversation) {
|
|
2020
4126
|
try {
|
|
2021
|
-
const exportDir =
|
|
2022
|
-
|
|
2023
|
-
const exportPath =
|
|
4127
|
+
const exportDir = path12.join(os11.homedir(), ".aman-agent", "exports");
|
|
4128
|
+
fs12.mkdirSync(exportDir, { recursive: true });
|
|
4129
|
+
const exportPath = path12.join(exportDir, `${sessionId}.md`);
|
|
2024
4130
|
const lines = [
|
|
2025
4131
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
2026
4132
|
`**Model:** ${model}`,
|
|
@@ -2034,19 +4140,19 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2034
4140
|
lines.push(`${label} ${msg.content}`, "");
|
|
2035
4141
|
}
|
|
2036
4142
|
}
|
|
2037
|
-
|
|
2038
|
-
console.log(
|
|
4143
|
+
fs12.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
4144
|
+
console.log(pc6.green(`Exported to ${exportPath}`));
|
|
2039
4145
|
} catch {
|
|
2040
|
-
console.log(
|
|
4146
|
+
console.log(pc6.red("Failed to export conversation."));
|
|
2041
4147
|
}
|
|
2042
4148
|
continue;
|
|
2043
4149
|
}
|
|
2044
4150
|
if (cmdResult.saveConversation && mcpManager) {
|
|
2045
4151
|
try {
|
|
2046
4152
|
await saveConversationToMemory(mcpManager, messages, sessionId);
|
|
2047
|
-
console.log(
|
|
4153
|
+
console.log(pc6.green("Conversation saved to memory."));
|
|
2048
4154
|
} catch {
|
|
2049
|
-
console.log(
|
|
4155
|
+
console.log(pc6.red("Failed to save conversation."));
|
|
2050
4156
|
}
|
|
2051
4157
|
continue;
|
|
2052
4158
|
}
|
|
@@ -2065,7 +4171,7 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2065
4171
|
const wfMatch = await onWorkflowMatch(input, hookCtx);
|
|
2066
4172
|
if (wfMatch) {
|
|
2067
4173
|
const useIt = await new Promise((resolve) => {
|
|
2068
|
-
rl.question(
|
|
4174
|
+
rl.question(pc6.dim(` Workflow "${wfMatch.name}" matches. Use it? (y/N) `), (answer) => resolve(answer.toLowerCase() === "y"));
|
|
2069
4175
|
});
|
|
2070
4176
|
if (useIt) {
|
|
2071
4177
|
activeSystemPrompt = systemPrompt + `
|
|
@@ -2073,114 +4179,212 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2073
4179
|
<active-workflow>
|
|
2074
4180
|
${wfMatch.steps}
|
|
2075
4181
|
</active-workflow>`;
|
|
2076
|
-
console.log(
|
|
4182
|
+
console.log(pc6.dim(` Using "${wfMatch.name}" workflow.`));
|
|
2077
4183
|
}
|
|
2078
4184
|
}
|
|
2079
4185
|
} catch (err) {
|
|
2080
4186
|
log.debug("agent", "workflow match failed", err);
|
|
2081
4187
|
}
|
|
2082
4188
|
}
|
|
4189
|
+
const activePlan = getActivePlan();
|
|
4190
|
+
if (activePlan) {
|
|
4191
|
+
activeSystemPrompt += "\n\n" + formatPlanForPrompt(activePlan);
|
|
4192
|
+
}
|
|
4193
|
+
if (mcpManager) {
|
|
4194
|
+
try {
|
|
4195
|
+
const skillContext = await autoTriggerSkills(input, mcpManager);
|
|
4196
|
+
if (skillContext) {
|
|
4197
|
+
activeSystemPrompt += "\n\n" + skillContext;
|
|
4198
|
+
}
|
|
4199
|
+
} catch (err) {
|
|
4200
|
+
log.debug("agent", "skill auto-trigger failed", err);
|
|
4201
|
+
}
|
|
4202
|
+
const knowledgeItem = matchKnowledge(input);
|
|
4203
|
+
if (knowledgeItem) {
|
|
4204
|
+
activeSystemPrompt += `
|
|
4205
|
+
|
|
4206
|
+
<knowledge name="${knowledgeItem.name}" category="${knowledgeItem.category}">
|
|
4207
|
+
${knowledgeItem.description}
|
|
4208
|
+
|
|
4209
|
+
${knowledgeItem.content}
|
|
4210
|
+
</knowledge>`;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
2083
4213
|
await trimConversation(messages, client);
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
4214
|
+
const textExts = /* @__PURE__ */ new Set([
|
|
4215
|
+
".txt",
|
|
4216
|
+
".md",
|
|
4217
|
+
".json",
|
|
4218
|
+
".js",
|
|
4219
|
+
".ts",
|
|
4220
|
+
".jsx",
|
|
4221
|
+
".tsx",
|
|
4222
|
+
".py",
|
|
4223
|
+
".html",
|
|
4224
|
+
".css",
|
|
4225
|
+
".yml",
|
|
4226
|
+
".yaml",
|
|
4227
|
+
".toml",
|
|
4228
|
+
".xml",
|
|
4229
|
+
".csv",
|
|
4230
|
+
".sh",
|
|
4231
|
+
".bash",
|
|
4232
|
+
".zsh",
|
|
4233
|
+
".env",
|
|
4234
|
+
".cfg",
|
|
4235
|
+
".ini",
|
|
4236
|
+
".log",
|
|
4237
|
+
".sql",
|
|
4238
|
+
".graphql",
|
|
4239
|
+
".rs",
|
|
4240
|
+
".go",
|
|
4241
|
+
".java",
|
|
4242
|
+
".rb",
|
|
4243
|
+
".php",
|
|
4244
|
+
".c",
|
|
4245
|
+
".cpp",
|
|
4246
|
+
".h",
|
|
4247
|
+
".swift",
|
|
4248
|
+
".kt",
|
|
4249
|
+
".r",
|
|
4250
|
+
".lua"
|
|
4251
|
+
]);
|
|
4252
|
+
const imageExts = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
4253
|
+
const docExts = /* @__PURE__ */ new Set([".docx", ".doc", ".pdf", ".pptx", ".ppt", ".xlsx", ".xls", ".odt", ".rtf", ".epub"]);
|
|
4254
|
+
const mimeMap = {
|
|
4255
|
+
".png": "image/png",
|
|
4256
|
+
".jpg": "image/jpeg",
|
|
4257
|
+
".jpeg": "image/jpeg",
|
|
4258
|
+
".gif": "image/gif",
|
|
4259
|
+
".webp": "image/webp",
|
|
4260
|
+
".bmp": "image/png"
|
|
4261
|
+
};
|
|
4262
|
+
const maxImageBytes = 20 * 1024 * 1024;
|
|
4263
|
+
let textContent = input;
|
|
4264
|
+
const imageBlocks = [];
|
|
4265
|
+
const filePathMatches = [...input.matchAll(/(\/[\w./-]+|~\/[\w./-]+)/g)];
|
|
4266
|
+
for (const match of filePathMatches) {
|
|
4267
|
+
let filePath = match[1];
|
|
2088
4268
|
if (filePath.startsWith("~/")) {
|
|
2089
|
-
filePath =
|
|
2090
|
-
}
|
|
2091
|
-
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
"
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
".rs",
|
|
2119
|
-
".go",
|
|
2120
|
-
".java",
|
|
2121
|
-
".rb",
|
|
2122
|
-
".php",
|
|
2123
|
-
".c",
|
|
2124
|
-
".cpp",
|
|
2125
|
-
".h",
|
|
2126
|
-
".swift",
|
|
2127
|
-
".kt",
|
|
2128
|
-
".r",
|
|
2129
|
-
".lua"
|
|
2130
|
-
]);
|
|
2131
|
-
if (textExts.has(ext) || ext === "") {
|
|
2132
|
-
try {
|
|
2133
|
-
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2134
|
-
const maxChars = 5e4;
|
|
2135
|
-
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
4269
|
+
filePath = path12.join(os11.homedir(), filePath.slice(2));
|
|
4270
|
+
}
|
|
4271
|
+
if (!fs12.existsSync(filePath) || !fs12.statSync(filePath).isFile()) continue;
|
|
4272
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
4273
|
+
if (imageExts.has(ext)) {
|
|
4274
|
+
try {
|
|
4275
|
+
const stat = fs12.statSync(filePath);
|
|
4276
|
+
if (stat.size > maxImageBytes) {
|
|
4277
|
+
process.stdout.write(pc6.yellow(` [skipped: ${path12.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
4278
|
+
`));
|
|
4279
|
+
continue;
|
|
4280
|
+
}
|
|
4281
|
+
const data = fs12.readFileSync(filePath).toString("base64");
|
|
4282
|
+
const mediaType = mimeMap[ext] || "image/png";
|
|
4283
|
+
imageBlocks.push({
|
|
4284
|
+
type: "image",
|
|
4285
|
+
source: { type: "base64", media_type: mediaType, data }
|
|
4286
|
+
});
|
|
4287
|
+
process.stdout.write(pc6.dim(` [attached image: ${path12.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
|
|
4288
|
+
`));
|
|
4289
|
+
} catch {
|
|
4290
|
+
process.stdout.write(pc6.dim(` [could not read image: ${filePath}]
|
|
4291
|
+
`));
|
|
4292
|
+
}
|
|
4293
|
+
} else if (textExts.has(ext) || ext === "") {
|
|
4294
|
+
try {
|
|
4295
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
4296
|
+
const maxChars = 5e4;
|
|
4297
|
+
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
2136
4298
|
|
|
2137
4299
|
[... truncated, ${content.length - maxChars} chars remaining]` : content;
|
|
2138
|
-
|
|
4300
|
+
textContent += `
|
|
2139
4301
|
|
|
2140
4302
|
<file path="${filePath}" size="${content.length} chars">
|
|
2141
4303
|
${trimmed}
|
|
2142
4304
|
</file>`;
|
|
2143
|
-
|
|
4305
|
+
process.stdout.write(pc6.dim(` [attached: ${path12.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
|
|
2144
4306
|
`));
|
|
2145
|
-
|
|
2146
|
-
|
|
4307
|
+
} catch {
|
|
4308
|
+
process.stdout.write(pc6.dim(` [could not read: ${filePath}]
|
|
2147
4309
|
`));
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
4310
|
+
}
|
|
4311
|
+
} else if (docExts.has(ext)) {
|
|
4312
|
+
if (mcpManager) {
|
|
4313
|
+
try {
|
|
4314
|
+
process.stdout.write(pc6.dim(` [converting: ${path12.basename(filePath)}...]
|
|
2153
4315
|
`));
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
4316
|
+
const converted = await mcpManager.callTool("doc_convert", { path: filePath });
|
|
4317
|
+
if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
|
|
4318
|
+
textContent += `
|
|
2157
4319
|
|
|
2158
4320
|
<file path="${filePath}" format="${ext}">
|
|
2159
4321
|
${converted.slice(0, 5e4)}
|
|
2160
4322
|
</file>`;
|
|
2161
|
-
|
|
4323
|
+
process.stdout.write(pc6.dim(` [attached: ${path12.basename(filePath)} (converted from ${ext})]
|
|
2162
4324
|
`));
|
|
2163
|
-
|
|
2164
|
-
|
|
4325
|
+
} else {
|
|
4326
|
+
textContent += `
|
|
2165
4327
|
|
|
2166
4328
|
<file-error path="${filePath}">
|
|
2167
4329
|
${converted}
|
|
2168
4330
|
</file-error>`;
|
|
2169
|
-
|
|
2170
|
-
`));
|
|
2171
|
-
}
|
|
2172
|
-
} catch {
|
|
2173
|
-
process.stdout.write(pc3.dim(` [could not convert: ${path7.basename(filePath)}]
|
|
4331
|
+
process.stdout.write(pc6.yellow(` [conversion note: ${converted.split("\n")[0]}]
|
|
2174
4332
|
`));
|
|
2175
4333
|
}
|
|
2176
|
-
}
|
|
2177
|
-
process.stdout.write(
|
|
4334
|
+
} catch {
|
|
4335
|
+
process.stdout.write(pc6.dim(` [could not convert: ${path12.basename(filePath)}]
|
|
2178
4336
|
`));
|
|
2179
4337
|
}
|
|
4338
|
+
} else {
|
|
4339
|
+
process.stdout.write(pc6.yellow(` Binary file (${ext}) \u2014 install Docling for document support: pip install docling
|
|
4340
|
+
`));
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
const urlImageMatches = [...input.matchAll(/https?:\/\/\S+\.(?:png|jpg|jpeg|gif|webp)(?:\?\S*)?/gi)];
|
|
4345
|
+
for (const match of urlImageMatches) {
|
|
4346
|
+
const url = match[0];
|
|
4347
|
+
try {
|
|
4348
|
+
process.stdout.write(pc6.dim(` [fetching image: ${url.slice(0, 60)}...]
|
|
4349
|
+
`));
|
|
4350
|
+
const response = await fetch(url);
|
|
4351
|
+
if (!response.ok) {
|
|
4352
|
+
process.stdout.write(pc6.yellow(` [could not fetch: HTTP ${response.status}]
|
|
4353
|
+
`));
|
|
4354
|
+
continue;
|
|
4355
|
+
}
|
|
4356
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
4357
|
+
if (buffer.length > maxImageBytes) {
|
|
4358
|
+
process.stdout.write(pc6.yellow(` [skipped: image URL exceeds 20MB limit]
|
|
4359
|
+
`));
|
|
4360
|
+
continue;
|
|
2180
4361
|
}
|
|
4362
|
+
const contentType = response.headers.get("content-type") || "";
|
|
4363
|
+
let mediaType = "image/png";
|
|
4364
|
+
if (contentType.includes("jpeg") || contentType.includes("jpg")) mediaType = "image/jpeg";
|
|
4365
|
+
else if (contentType.includes("gif")) mediaType = "image/gif";
|
|
4366
|
+
else if (contentType.includes("webp")) mediaType = "image/webp";
|
|
4367
|
+
else if (contentType.includes("png")) mediaType = "image/png";
|
|
4368
|
+
imageBlocks.push({
|
|
4369
|
+
type: "image",
|
|
4370
|
+
source: { type: "base64", media_type: mediaType, data: buffer.toString("base64") }
|
|
4371
|
+
});
|
|
4372
|
+
process.stdout.write(pc6.dim(` [attached image URL: (${(buffer.length / 1024).toFixed(1)}KB)]
|
|
4373
|
+
`));
|
|
4374
|
+
} catch {
|
|
4375
|
+
process.stdout.write(pc6.dim(` [could not fetch image: ${url}]
|
|
4376
|
+
`));
|
|
2181
4377
|
}
|
|
2182
4378
|
}
|
|
2183
|
-
|
|
4379
|
+
if (imageBlocks.length > 0) {
|
|
4380
|
+
const blocks = [
|
|
4381
|
+
{ type: "text", text: textContent },
|
|
4382
|
+
...imageBlocks
|
|
4383
|
+
];
|
|
4384
|
+
messages.push({ role: "user", content: blocks });
|
|
4385
|
+
} else {
|
|
4386
|
+
messages.push({ role: "user", content: textContent });
|
|
4387
|
+
}
|
|
2184
4388
|
let augmentedSystemPrompt = activeSystemPrompt;
|
|
2185
4389
|
let memoryTokens = 0;
|
|
2186
4390
|
if (mcpManager) {
|
|
@@ -2190,15 +4394,39 @@ ${converted}
|
|
|
2190
4394
|
memoryTokens = recall.tokenEstimate;
|
|
2191
4395
|
}
|
|
2192
4396
|
}
|
|
4397
|
+
const userTurnCount = messages.filter((m) => m.role === "user").length;
|
|
4398
|
+
if (mcpManager && hooksConfig?.personalityAdapt !== false && userTurnCount > 0 && userTurnCount % 5 === 0) {
|
|
4399
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
4400
|
+
let period;
|
|
4401
|
+
if (hour < 6) period = "late-night";
|
|
4402
|
+
else if (hour < 12) period = "morning";
|
|
4403
|
+
else if (hour < 17) period = "afternoon";
|
|
4404
|
+
else if (hour < 21) period = "evening";
|
|
4405
|
+
else period = "night";
|
|
4406
|
+
const recentUserMsgs = messages.filter((m) => m.role === "user" && typeof m.content === "string").slice(-5).map((m) => m.content);
|
|
4407
|
+
const sessionMinutes = Math.round((Date.now() - getSessionStartTime()) / 6e4);
|
|
4408
|
+
const state = computePersonality({
|
|
4409
|
+
timePeriod: period,
|
|
4410
|
+
sessionMinutes,
|
|
4411
|
+
turnCount: userTurnCount,
|
|
4412
|
+
recentMessages: recentUserMsgs
|
|
4413
|
+
});
|
|
4414
|
+
syncPersonalityToCore(state, mcpManager).catch(() => {
|
|
4415
|
+
});
|
|
4416
|
+
const nudge = formatWellbeingNudge(state);
|
|
4417
|
+
if (nudge) {
|
|
4418
|
+
augmentedSystemPrompt += "\n" + nudge;
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
2193
4421
|
const divider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - aiName.length - 2);
|
|
2194
4422
|
process.stdout.write(`
|
|
2195
|
-
${
|
|
4423
|
+
${pc6.cyan(pc6.bold(aiName))} ${pc6.dim(divider)}
|
|
2196
4424
|
|
|
2197
4425
|
`);
|
|
2198
4426
|
try {
|
|
2199
4427
|
let response = await withRetry(
|
|
2200
4428
|
() => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
|
|
2201
|
-
{ maxAttempts: 3, baseDelay: 1e3, retryable:
|
|
4429
|
+
{ maxAttempts: 3, baseDelay: 1e3, retryable: isRetryable2 }
|
|
2202
4430
|
);
|
|
2203
4431
|
messages.push(response.message);
|
|
2204
4432
|
while (response.toolUses.length > 0 && mcpManager) {
|
|
@@ -2208,7 +4436,7 @@ ${converted}
|
|
|
2208
4436
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
2209
4437
|
const check = await onBeforeToolExec(toolUse.name, toolUse.input, hookCtx);
|
|
2210
4438
|
if (!check.allow) {
|
|
2211
|
-
process.stdout.write(
|
|
4439
|
+
process.stdout.write(pc6.red(` [BLOCKED: ${check.reason}]
|
|
2212
4440
|
`));
|
|
2213
4441
|
return {
|
|
2214
4442
|
type: "tool_result",
|
|
@@ -2218,7 +4446,49 @@ ${converted}
|
|
|
2218
4446
|
};
|
|
2219
4447
|
}
|
|
2220
4448
|
}
|
|
2221
|
-
|
|
4449
|
+
if (toolUse.name === "delegate_task" && mcpManager) {
|
|
4450
|
+
const input2 = toolUse.input;
|
|
4451
|
+
process.stdout.write(pc6.dim(`
|
|
4452
|
+
[delegating to ${input2.profile}...]
|
|
4453
|
+
|
|
4454
|
+
`));
|
|
4455
|
+
const result2 = await delegateTask(input2.task, input2.profile, client, mcpManager, { tools });
|
|
4456
|
+
const output = result2.success ? `[${input2.profile}] completed:
|
|
4457
|
+
|
|
4458
|
+
${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
4459
|
+
return {
|
|
4460
|
+
type: "tool_result",
|
|
4461
|
+
tool_use_id: toolUse.id,
|
|
4462
|
+
content: output
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
if (toolUse.name === "team_run" && mcpManager) {
|
|
4466
|
+
const input2 = toolUse.input;
|
|
4467
|
+
const team = loadTeam(input2.team);
|
|
4468
|
+
if (!team) {
|
|
4469
|
+
return {
|
|
4470
|
+
type: "tool_result",
|
|
4471
|
+
tool_use_id: toolUse.id,
|
|
4472
|
+
content: `Team not found: ${input2.team}`,
|
|
4473
|
+
is_error: true
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
const result2 = await runTeam(team, input2.task, client, mcpManager, tools);
|
|
4477
|
+
return {
|
|
4478
|
+
type: "tool_result",
|
|
4479
|
+
tool_use_id: toolUse.id,
|
|
4480
|
+
content: result2.success ? formatTeamResult(result2) : `Team execution failed: ${result2.finalOutput}`
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
if (shouldRunInBackground(toolUse.name)) {
|
|
4484
|
+
const task = bgTasks.launch(toolUse.name, toolUse.id, mcpManager, toolUse.input);
|
|
4485
|
+
return {
|
|
4486
|
+
type: "tool_result",
|
|
4487
|
+
tool_use_id: toolUse.id,
|
|
4488
|
+
content: `[${toolUse.name} launched in background (${task.id}). Results will appear when ready. Continue with other work.]`
|
|
4489
|
+
};
|
|
4490
|
+
}
|
|
4491
|
+
process.stdout.write(pc6.dim(` [using ${toolUse.name}...]
|
|
2222
4492
|
`));
|
|
2223
4493
|
const result = await mcpManager.callTool(toolUse.name, toolUse.input);
|
|
2224
4494
|
const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
|
|
@@ -2243,7 +4513,7 @@ ${converted}
|
|
|
2243
4513
|
});
|
|
2244
4514
|
response = await withRetry(
|
|
2245
4515
|
() => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
|
|
2246
|
-
{ maxAttempts: 3, baseDelay: 1e3, retryable:
|
|
4516
|
+
{ maxAttempts: 3, baseDelay: 1e3, retryable: isRetryable2 }
|
|
2247
4517
|
);
|
|
2248
4518
|
messages.push(response.message);
|
|
2249
4519
|
}
|
|
@@ -2251,7 +4521,7 @@ ${converted}
|
|
|
2251
4521
|
if (memoryTokens > 0) footerParts.push(`memories: ~${memoryTokens} tokens`);
|
|
2252
4522
|
const footer = footerParts.length > 0 ? ` ${footerParts.join(" | ")}` : "";
|
|
2253
4523
|
const footerDivider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - footer.length - 1);
|
|
2254
|
-
process.stdout.write(
|
|
4524
|
+
process.stdout.write(pc6.dim(` ${footerDivider}${footer}
|
|
2255
4525
|
`));
|
|
2256
4526
|
if (mcpManager && hooksConfig?.extractMemories) {
|
|
2257
4527
|
const assistantText = typeof response.message.content === "string" ? response.message.content : response.message.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
@@ -2264,7 +4534,7 @@ ${converted}
|
|
|
2264
4534
|
extractorState
|
|
2265
4535
|
);
|
|
2266
4536
|
if (count > 0) {
|
|
2267
|
-
process.stdout.write(
|
|
4537
|
+
process.stdout.write(pc6.dim(` [${count} memory${count > 1 ? "ies" : ""} stored]
|
|
2268
4538
|
`));
|
|
2269
4539
|
}
|
|
2270
4540
|
}
|
|
@@ -2273,11 +4543,11 @@ ${converted}
|
|
|
2273
4543
|
}
|
|
2274
4544
|
if (hooksConfig?.featureHints) {
|
|
2275
4545
|
hintState.turnCount++;
|
|
2276
|
-
const hasWorkflows =
|
|
4546
|
+
const hasWorkflows = fs12.existsSync(path12.join(os11.homedir(), ".aflow", "flow.md"));
|
|
2277
4547
|
const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
|
|
2278
4548
|
const hint = getHint(hintState, { hasWorkflows, memoryCount });
|
|
2279
4549
|
if (hint) {
|
|
2280
|
-
process.stdout.write(
|
|
4550
|
+
process.stdout.write(pc6.dim(` ${hint}
|
|
2281
4551
|
`));
|
|
2282
4552
|
saveShownHints(hintState.shownHints);
|
|
2283
4553
|
}
|
|
@@ -2285,7 +4555,7 @@ ${converted}
|
|
|
2285
4555
|
} catch (error) {
|
|
2286
4556
|
const rawMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
2287
4557
|
const friendly = humanizeError(rawMessage);
|
|
2288
|
-
console.error(
|
|
4558
|
+
console.error(pc6.red(`
|
|
2289
4559
|
${friendly}`));
|
|
2290
4560
|
}
|
|
2291
4561
|
}
|
|
@@ -2307,9 +4577,9 @@ async function saveConversationToMemory(mcpManager, messages, sessionId) {
|
|
|
2307
4577
|
}
|
|
2308
4578
|
|
|
2309
4579
|
// src/index.ts
|
|
2310
|
-
import
|
|
2311
|
-
import
|
|
2312
|
-
import
|
|
4580
|
+
import fs13 from "fs";
|
|
4581
|
+
import path13 from "path";
|
|
4582
|
+
import os12 from "os";
|
|
2313
4583
|
|
|
2314
4584
|
// src/presets.ts
|
|
2315
4585
|
var PRESETS = {
|
|
@@ -2418,9 +4688,9 @@ ${wfSections}`;
|
|
|
2418
4688
|
|
|
2419
4689
|
// src/index.ts
|
|
2420
4690
|
async function autoDetectConfig() {
|
|
2421
|
-
const reconfigMarker =
|
|
2422
|
-
if (
|
|
2423
|
-
|
|
4691
|
+
const reconfigMarker = path13.join(os12.homedir(), ".aman-agent", ".reconfig");
|
|
4692
|
+
if (fs13.existsSync(reconfigMarker)) {
|
|
4693
|
+
fs13.unlinkSync(reconfigMarker);
|
|
2424
4694
|
return null;
|
|
2425
4695
|
}
|
|
2426
4696
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -2449,11 +4719,11 @@ async function autoDetectConfig() {
|
|
|
2449
4719
|
return null;
|
|
2450
4720
|
}
|
|
2451
4721
|
function bootstrapEcosystem() {
|
|
2452
|
-
const home2 =
|
|
2453
|
-
const corePath =
|
|
2454
|
-
if (
|
|
2455
|
-
|
|
2456
|
-
|
|
4722
|
+
const home2 = os12.homedir();
|
|
4723
|
+
const corePath = path13.join(home2, ".acore", "core.md");
|
|
4724
|
+
if (fs13.existsSync(corePath)) return false;
|
|
4725
|
+
fs13.mkdirSync(path13.join(home2, ".acore"), { recursive: true });
|
|
4726
|
+
fs13.writeFileSync(corePath, [
|
|
2457
4727
|
"# Aman",
|
|
2458
4728
|
"",
|
|
2459
4729
|
"## Personality",
|
|
@@ -2465,11 +4735,11 @@ function bootstrapEcosystem() {
|
|
|
2465
4735
|
"## Session",
|
|
2466
4736
|
"_New companion \u2014 no prior sessions._"
|
|
2467
4737
|
].join("\n"), "utf-8");
|
|
2468
|
-
const rulesDir =
|
|
2469
|
-
const rulesPath =
|
|
2470
|
-
if (!
|
|
2471
|
-
|
|
2472
|
-
|
|
4738
|
+
const rulesDir = path13.join(home2, ".arules");
|
|
4739
|
+
const rulesPath = path13.join(rulesDir, "rules.md");
|
|
4740
|
+
if (!fs13.existsSync(rulesPath)) {
|
|
4741
|
+
fs13.mkdirSync(rulesDir, { recursive: true });
|
|
4742
|
+
fs13.writeFileSync(rulesPath, [
|
|
2473
4743
|
"# Guardrails",
|
|
2474
4744
|
"",
|
|
2475
4745
|
"## safety",
|
|
@@ -2484,16 +4754,16 @@ function bootstrapEcosystem() {
|
|
|
2484
4754
|
return true;
|
|
2485
4755
|
}
|
|
2486
4756
|
var program = new Command();
|
|
2487
|
-
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) => {
|
|
2488
|
-
p2.intro(
|
|
4757
|
+
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).option("--profile <name>", "Use a specific agent profile (e.g., coder, writer, researcher)").action(async (options) => {
|
|
4758
|
+
p2.intro(pc7.bold("aman agent") + pc7.dim(" \u2014 your AI companion"));
|
|
2489
4759
|
let config = loadConfig();
|
|
2490
4760
|
if (!config) {
|
|
2491
4761
|
const detected = await autoDetectConfig();
|
|
2492
4762
|
if (detected) {
|
|
2493
4763
|
config = detected;
|
|
2494
4764
|
const providerLabel = detected.provider === "anthropic" ? "Anthropic API key" : detected.provider === "openai" ? "OpenAI API key" : "Ollama";
|
|
2495
|
-
p2.log.success(`Auto-detected ${providerLabel}. Using ${
|
|
2496
|
-
p2.log.info(
|
|
4765
|
+
p2.log.success(`Auto-detected ${providerLabel}. Using ${pc7.bold(detected.model)}.`);
|
|
4766
|
+
p2.log.info(pc7.dim("Change anytime with /reconfig"));
|
|
2497
4767
|
saveConfig(config);
|
|
2498
4768
|
} else {
|
|
2499
4769
|
p2.log.info("First-time setup \u2014 configure your LLM connection.");
|
|
@@ -2524,7 +4794,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2524
4794
|
defaultModel = modelInput || "llama3.2";
|
|
2525
4795
|
} else if (provider === "anthropic") {
|
|
2526
4796
|
p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
|
|
2527
|
-
p2.log.info(
|
|
4797
|
+
p2.log.info(pc7.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
|
|
2528
4798
|
apiKey = await p2.text({
|
|
2529
4799
|
message: "API key (starts with sk-ant-)",
|
|
2530
4800
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
@@ -2590,29 +4860,27 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2590
4860
|
const s = p2.spinner();
|
|
2591
4861
|
s.start("Loading ecosystem");
|
|
2592
4862
|
bootstrapEcosystem();
|
|
4863
|
+
const profile = options.profile || process.env.AMAN_PROFILE || void 0;
|
|
4864
|
+
if (profile) {
|
|
4865
|
+
p2.log.info(`Profile: ${pc7.bold(profile)}`);
|
|
4866
|
+
}
|
|
2593
4867
|
const budget = options.budget || void 0;
|
|
2594
|
-
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
4868
|
+
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget, profile);
|
|
2595
4869
|
s.stop("Ecosystem loaded");
|
|
2596
4870
|
if (layers.length === 0) {
|
|
2597
4871
|
p2.log.warning(
|
|
2598
|
-
"No ecosystem configured. Run " +
|
|
4872
|
+
"No ecosystem configured. Run " + pc7.bold("npx @aman_asmuei/aman") + " first."
|
|
2599
4873
|
);
|
|
2600
4874
|
p2.log.info("Starting with empty system prompt.");
|
|
2601
4875
|
} else {
|
|
2602
4876
|
p2.log.success(
|
|
2603
|
-
`Loaded: ${layers.join(", ")} ${
|
|
4877
|
+
`Loaded: ${layers.join(", ")} ${pc7.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
|
|
2604
4878
|
);
|
|
2605
4879
|
if (truncated.length > 0) {
|
|
2606
|
-
p2.log.warning(`Truncated: ${truncated.join(", ")} ${
|
|
4880
|
+
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc7.dim("(over budget)")}`);
|
|
2607
4881
|
}
|
|
2608
4882
|
}
|
|
2609
|
-
const
|
|
2610
|
-
let aiName = "Assistant";
|
|
2611
|
-
if (fs8.existsSync(corePath)) {
|
|
2612
|
-
const content = fs8.readFileSync(corePath, "utf-8");
|
|
2613
|
-
const match = content.match(/^# (.+)$/m);
|
|
2614
|
-
if (match) aiName = match[1];
|
|
2615
|
-
}
|
|
4883
|
+
const aiName = getProfileAiName(profile);
|
|
2616
4884
|
const mcpManager = new McpManager();
|
|
2617
4885
|
const mcpSpinner = p2.spinner();
|
|
2618
4886
|
mcpSpinner.start("Connecting to MCP servers");
|
|
@@ -2639,7 +4907,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2639
4907
|
memSpinner.stop("Memory consolidated");
|
|
2640
4908
|
if (report.merged > 0 || report.pruned > 0 || report.promoted > 0) {
|
|
2641
4909
|
p2.log.info(
|
|
2642
|
-
`Memory health: ${report.healthScore ?? "?"}% ` +
|
|
4910
|
+
`Memory health: ${report.healthScore ?? "?"}% ` + pc7.dim(`(merged ${report.merged}, pruned ${report.pruned}, promoted ${report.promoted})`)
|
|
2643
4911
|
);
|
|
2644
4912
|
}
|
|
2645
4913
|
} catch {
|
|
@@ -2670,7 +4938,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2670
4938
|
} else {
|
|
2671
4939
|
client = createOpenAIClient(config.apiKey, model);
|
|
2672
4940
|
}
|
|
2673
|
-
p2.log.success(`${
|
|
4941
|
+
p2.log.success(`${pc7.bold(aiName)} is ready. Model: ${pc7.dim(model)}`);
|
|
2674
4942
|
await runAgent(
|
|
2675
4943
|
client,
|
|
2676
4944
|
systemPrompt,
|
|
@@ -2683,7 +4951,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2683
4951
|
await mcpManager.disconnect();
|
|
2684
4952
|
});
|
|
2685
4953
|
program.command("init").description("Set up your AI companion with a guided wizard").action(async () => {
|
|
2686
|
-
p2.intro(
|
|
4954
|
+
p2.intro(pc7.bold("aman agent init") + pc7.dim(" \u2014 set up your companion"));
|
|
2687
4955
|
const name = await p2.text({
|
|
2688
4956
|
message: "What should your companion be called?",
|
|
2689
4957
|
placeholder: "Aman",
|
|
@@ -2703,19 +4971,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
2703
4971
|
});
|
|
2704
4972
|
if (p2.isCancel(preset)) process.exit(0);
|
|
2705
4973
|
const result = applyPreset(preset, name || "Aman");
|
|
2706
|
-
const home2 =
|
|
2707
|
-
|
|
2708
|
-
|
|
4974
|
+
const home2 = os12.homedir();
|
|
4975
|
+
fs13.mkdirSync(path13.join(home2, ".acore"), { recursive: true });
|
|
4976
|
+
fs13.writeFileSync(path13.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
|
|
2709
4977
|
p2.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
|
|
2710
4978
|
if (result.rulesMd) {
|
|
2711
|
-
|
|
2712
|
-
|
|
4979
|
+
fs13.mkdirSync(path13.join(home2, ".arules"), { recursive: true });
|
|
4980
|
+
fs13.writeFileSync(path13.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
|
|
2713
4981
|
const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
|
|
2714
4982
|
p2.log.success(`${ruleCount} rules set`);
|
|
2715
4983
|
}
|
|
2716
4984
|
if (result.flowMd) {
|
|
2717
|
-
|
|
2718
|
-
|
|
4985
|
+
fs13.mkdirSync(path13.join(home2, ".aflow"), { recursive: true });
|
|
4986
|
+
fs13.writeFileSync(path13.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
|
|
2719
4987
|
const wfCount = (result.flowMd.match(/^## /gm) || []).length;
|
|
2720
4988
|
p2.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
|
|
2721
4989
|
}
|
|
@@ -2723,16 +4991,16 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
2723
4991
|
p2.outro("Your companion is ready.");
|
|
2724
4992
|
console.log("");
|
|
2725
4993
|
if (isNpx) {
|
|
2726
|
-
console.log(` ${
|
|
4994
|
+
console.log(` ${pc7.bold("Start chatting:")} npx @aman_asmuei/aman-agent`);
|
|
2727
4995
|
console.log("");
|
|
2728
|
-
console.log(` ${
|
|
2729
|
-
console.log(` ${
|
|
4996
|
+
console.log(` ${pc7.dim("Tip: Install globally to use")} ${pc7.bold("aman-agent")} ${pc7.dim("directly:")}`);
|
|
4997
|
+
console.log(` ${pc7.dim(" npm install -g @aman_asmuei/aman-agent")}`);
|
|
2730
4998
|
} else {
|
|
2731
|
-
console.log(` ${
|
|
4999
|
+
console.log(` ${pc7.bold("Start chatting:")} aman-agent`);
|
|
2732
5000
|
}
|
|
2733
5001
|
console.log("");
|
|
2734
|
-
console.log(` ${
|
|
2735
|
-
console.log(` ${
|
|
5002
|
+
console.log(` ${pc7.dim("Add tools:")} npx @aman_asmuei/akit add github`);
|
|
5003
|
+
console.log(` ${pc7.dim("Browse:")} npx @aman_asmuei/akit search <query>`);
|
|
2736
5004
|
console.log("");
|
|
2737
5005
|
});
|
|
2738
5006
|
program.parse();
|