@aman_asmuei/aman 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +47 -0
- package/deploy/systemd/aman.service +29 -0
- package/dist/index.js +389 -19
- package/docker-compose.ollama.yml +65 -0
- package/docker-compose.yml +40 -0
- package/docker-entrypoint.sh +165 -0
- package/package.json +7 -2
package/Dockerfile
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# aman ecosystem — full AI companion in one container
|
|
2
|
+
# Includes: aman-agent (CLI) + achannel (Telegram/Discord/webhook) + aman-mcp + amem
|
|
3
|
+
#
|
|
4
|
+
# Build: docker build -t aman .
|
|
5
|
+
# Run: docker run -it -e ANTHROPIC_API_KEY=sk-... aman
|
|
6
|
+
# Serve: docker run -d -p 3000:3000 -e ANTHROPIC_API_KEY=sk-... aman serve
|
|
7
|
+
|
|
8
|
+
FROM node:22-alpine AS base
|
|
9
|
+
|
|
10
|
+
# Install system dependencies
|
|
11
|
+
RUN apk add --no-cache git curl
|
|
12
|
+
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
# Install all ecosystem packages globally
|
|
16
|
+
RUN npm install -g \
|
|
17
|
+
@aman_asmuei/aman-agent@latest \
|
|
18
|
+
@aman_asmuei/aman-mcp@latest \
|
|
19
|
+
@aman_asmuei/achannel@latest \
|
|
20
|
+
@aman_asmuei/aman@latest
|
|
21
|
+
|
|
22
|
+
# Create ecosystem directories
|
|
23
|
+
RUN mkdir -p /root/.acore /root/.amem /root/.akit /root/.aflow \
|
|
24
|
+
/root/.arules /root/.aeval /root/.askill /root/.aman-agent
|
|
25
|
+
|
|
26
|
+
# Default environment
|
|
27
|
+
ENV NODE_ENV=production
|
|
28
|
+
ENV AMEM_DB_PATH=/root/.amem/memory.db
|
|
29
|
+
|
|
30
|
+
# Volumes for persistent data
|
|
31
|
+
VOLUME ["/root/.acore", "/root/.amem", "/root/.aman-agent"]
|
|
32
|
+
|
|
33
|
+
# Expose webhook port (achannel serve)
|
|
34
|
+
EXPOSE 3000
|
|
35
|
+
|
|
36
|
+
# Healthcheck for server mode
|
|
37
|
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
|
38
|
+
CMD curl -f http://localhost:3000/status || exit 1
|
|
39
|
+
|
|
40
|
+
# Entrypoint script
|
|
41
|
+
COPY docker-entrypoint.sh /usr/local/bin/
|
|
42
|
+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
43
|
+
|
|
44
|
+
ENTRYPOINT ["docker-entrypoint.sh"]
|
|
45
|
+
|
|
46
|
+
# Default: interactive CLI mode
|
|
47
|
+
CMD ["agent"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=aman AI companion server
|
|
3
|
+
After=network.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=aman
|
|
9
|
+
WorkingDirectory=/home/aman
|
|
10
|
+
ExecStart=/usr/bin/npx @aman_asmuei/achannel serve
|
|
11
|
+
Restart=always
|
|
12
|
+
RestartSec=10
|
|
13
|
+
|
|
14
|
+
# Environment — set your API key
|
|
15
|
+
EnvironmentFile=/home/aman/.aman-agent/env
|
|
16
|
+
|
|
17
|
+
# Logging
|
|
18
|
+
StandardOutput=journal
|
|
19
|
+
StandardError=journal
|
|
20
|
+
SyslogIdentifier=aman
|
|
21
|
+
|
|
22
|
+
# Security hardening
|
|
23
|
+
NoNewPrivileges=true
|
|
24
|
+
ProtectSystem=strict
|
|
25
|
+
ProtectHome=read-only
|
|
26
|
+
ReadWritePaths=/home/aman/.acore /home/aman/.amem /home/aman/.aman-agent /home/aman/.aeval /home/aman/.arules /home/aman/.aflow /home/aman/.akit /home/aman/.askill
|
|
27
|
+
|
|
28
|
+
[Install]
|
|
29
|
+
WantedBy=multi-user.target
|
package/dist/index.js
CHANGED
|
@@ -32,8 +32,8 @@ function detectPlatform(cwd) {
|
|
|
32
32
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
33
33
|
const parsed = JSON.parse(raw);
|
|
34
34
|
if (typeof parsed.platform === "string") {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
35
|
+
const p4 = parsed.platform;
|
|
36
|
+
if (p4 === "claude-code" || p4 === "cursor" || p4 === "windsurf") return p4;
|
|
37
37
|
}
|
|
38
38
|
} catch {
|
|
39
39
|
}
|
|
@@ -132,13 +132,23 @@ function detectEcosystem() {
|
|
|
132
132
|
} catch {
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
const askillPath = path.join(home, ".askill", "skills.md");
|
|
136
|
+
let askillCount = 0;
|
|
137
|
+
if (fs.existsSync(askillPath)) {
|
|
138
|
+
try {
|
|
139
|
+
const content = fs.readFileSync(askillPath, "utf-8");
|
|
140
|
+
askillCount = (content.match(/^## /gm) || []).length;
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
135
144
|
return {
|
|
136
145
|
acore: { installed: fs.existsSync(acorePath), path: acorePath },
|
|
137
146
|
amem: { installed: amemInstalled },
|
|
138
147
|
akit: { installed: fs.existsSync(akitPath), path: akitPath, toolCount: akitToolCount },
|
|
139
148
|
aflow: { installed: fs.existsSync(aflowPath), path: aflowPath, workflowCount: aflowWorkflowCount },
|
|
140
149
|
arules: { installed: fs.existsSync(arulesPath), path: arulesPath, ruleCount: arulesRuleCount },
|
|
141
|
-
aeval: { installed: fs.existsSync(aevalPath), path: aevalPath, sessions: aevalSessions }
|
|
150
|
+
aeval: { installed: fs.existsSync(aevalPath), path: aevalPath, sessions: aevalSessions },
|
|
151
|
+
askill: { installed: fs.existsSync(askillPath), path: askillPath, skillCount: askillCount }
|
|
142
152
|
};
|
|
143
153
|
}
|
|
144
154
|
|
|
@@ -246,6 +256,93 @@ function getPlatformFile(platform) {
|
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
|
|
259
|
+
// src/templates.ts
|
|
260
|
+
var STARTER_FLOW = `# My Workflows
|
|
261
|
+
|
|
262
|
+
## code-review
|
|
263
|
+
When asked to review code:
|
|
264
|
+
1. Analyze for bugs, logic errors, and edge cases
|
|
265
|
+
2. Check for security vulnerabilities (OWASP top 10)
|
|
266
|
+
3. Evaluate code style and maintainability
|
|
267
|
+
4. Summarize findings with severity ratings (critical/warning/info)
|
|
268
|
+
5. Suggest specific fixes with code examples
|
|
269
|
+
|
|
270
|
+
## bug-fix
|
|
271
|
+
When asked to fix a bug:
|
|
272
|
+
1. Reproduce \u2014 understand the expected vs actual behavior
|
|
273
|
+
2. Locate \u2014 find the root cause in the codebase
|
|
274
|
+
3. Fix \u2014 implement the minimal change that fixes the issue
|
|
275
|
+
4. Test \u2014 verify the fix works and doesn't break other things
|
|
276
|
+
5. Document \u2014 explain what was wrong and why the fix works
|
|
277
|
+
|
|
278
|
+
## feature-build
|
|
279
|
+
When asked to build a feature:
|
|
280
|
+
1. Clarify \u2014 ask questions until requirements are clear
|
|
281
|
+
2. Design \u2014 propose an approach, get approval
|
|
282
|
+
3. Implement \u2014 write the code in small, testable increments
|
|
283
|
+
4. Test \u2014 write and run tests for the new code
|
|
284
|
+
5. Review \u2014 check for edge cases, security, and performance
|
|
285
|
+
|
|
286
|
+
## daily-standup
|
|
287
|
+
When starting a new session:
|
|
288
|
+
1. Check git log for recent changes
|
|
289
|
+
2. Review open PRs and issues
|
|
290
|
+
3. Summarize: what was done, what's in progress, what's blocked
|
|
291
|
+
4. Ask what to focus on today
|
|
292
|
+
`;
|
|
293
|
+
var STARTER_RULES = `# My AI Rules
|
|
294
|
+
|
|
295
|
+
## Always
|
|
296
|
+
- Ask before deleting files or data
|
|
297
|
+
- Explain your reasoning before making changes
|
|
298
|
+
- Flag security concerns immediately
|
|
299
|
+
- Respect code review processes
|
|
300
|
+
|
|
301
|
+
## Never
|
|
302
|
+
- Push directly to main/master without approval
|
|
303
|
+
- Expose secrets, API keys, or credentials in code
|
|
304
|
+
- Make changes to production systems without confirmation
|
|
305
|
+
- Skip tests when fixing bugs
|
|
306
|
+
|
|
307
|
+
## Coding
|
|
308
|
+
- Follow existing code style and conventions
|
|
309
|
+
- Prefer simple solutions over clever ones
|
|
310
|
+
- Write tests for new functionality
|
|
311
|
+
- Keep PRs focused and small
|
|
312
|
+
|
|
313
|
+
## Communication
|
|
314
|
+
- Be direct \u2014 lead with the answer
|
|
315
|
+
- Admit when you don't know something
|
|
316
|
+
- Ask clarifying questions before assuming
|
|
317
|
+
- Flag when a task is outside your expertise
|
|
318
|
+
|
|
319
|
+
## Data
|
|
320
|
+
- Never log or expose personal information
|
|
321
|
+
- Treat user data as confidential
|
|
322
|
+
- Ask before accessing external APIs
|
|
323
|
+
- Don't store credentials in plain text
|
|
324
|
+
|
|
325
|
+
## Team
|
|
326
|
+
- Follow the team's branching strategy
|
|
327
|
+
- Respect code ownership boundaries
|
|
328
|
+
- Tag relevant people for review
|
|
329
|
+
- Document breaking changes
|
|
330
|
+
`;
|
|
331
|
+
var STARTER_EVAL = `# AI Relationship Metrics
|
|
332
|
+
|
|
333
|
+
## Overview
|
|
334
|
+
- Sessions: 0
|
|
335
|
+
- First session: {{DATE}}
|
|
336
|
+
- Trust level: 3/5
|
|
337
|
+
- Trajectory: building
|
|
338
|
+
|
|
339
|
+
## Timeline
|
|
340
|
+
|
|
341
|
+
## Milestones
|
|
342
|
+
|
|
343
|
+
## Patterns
|
|
344
|
+
`;
|
|
345
|
+
|
|
249
346
|
// src/commands/setup.ts
|
|
250
347
|
var ARCHETYPES = {
|
|
251
348
|
pragmatist: {
|
|
@@ -366,6 +463,74 @@ async function setupCommand() {
|
|
|
366
463
|
if (stack) inferredParts.push(stack);
|
|
367
464
|
p.log.info(`Inferred: ${pc.dim(inferredParts.join(" \xB7 "))}`);
|
|
368
465
|
}
|
|
466
|
+
const home = os2.homedir();
|
|
467
|
+
const aflowExists = ecosystem.aflow.installed;
|
|
468
|
+
const arulesExists = ecosystem.arules.installed;
|
|
469
|
+
const aevalExists = ecosystem.aeval.installed;
|
|
470
|
+
if (aflowExists && arulesExists && aevalExists) {
|
|
471
|
+
p.log.success(`Workflows: ${ecosystem.aflow.workflowCount} defined`);
|
|
472
|
+
p.log.success(`Guardrails: ${ecosystem.arules.ruleCount} rules`);
|
|
473
|
+
p.log.success("Evaluation: already configured");
|
|
474
|
+
} else {
|
|
475
|
+
const setupChoice = await p.select({
|
|
476
|
+
message: "Set up additional layers?",
|
|
477
|
+
options: [
|
|
478
|
+
{ value: "all", label: "Yes, set up everything", hint: "recommended" },
|
|
479
|
+
{ value: "choose", label: "Let me choose" },
|
|
480
|
+
{ value: "skip", label: "Skip for now" }
|
|
481
|
+
],
|
|
482
|
+
initialValue: "all"
|
|
483
|
+
});
|
|
484
|
+
if (p.isCancel(setupChoice)) process.exit(0);
|
|
485
|
+
let doFlow = false;
|
|
486
|
+
let doRules = false;
|
|
487
|
+
let doEval = false;
|
|
488
|
+
if (setupChoice === "all") {
|
|
489
|
+
doFlow = !aflowExists;
|
|
490
|
+
doRules = !arulesExists;
|
|
491
|
+
doEval = !aevalExists;
|
|
492
|
+
} else if (setupChoice === "choose") {
|
|
493
|
+
const layers = await p.multiselect({
|
|
494
|
+
message: "Which layers?",
|
|
495
|
+
options: [
|
|
496
|
+
...!aflowExists ? [{ value: "flow", label: "Workflows", hint: "4 starter workflows" }] : [],
|
|
497
|
+
...!arulesExists ? [{ value: "rules", label: "Guardrails", hint: "24 starter rules" }] : [],
|
|
498
|
+
...!aevalExists ? [{ value: "eval", label: "Evaluation", hint: "track relationship metrics" }] : []
|
|
499
|
+
],
|
|
500
|
+
required: false
|
|
501
|
+
});
|
|
502
|
+
if (p.isCancel(layers)) process.exit(0);
|
|
503
|
+
const selected = layers;
|
|
504
|
+
doFlow = selected.includes("flow");
|
|
505
|
+
doRules = selected.includes("rules");
|
|
506
|
+
doEval = selected.includes("eval");
|
|
507
|
+
}
|
|
508
|
+
if (doFlow) {
|
|
509
|
+
const aflowDir = path3.join(home, ".aflow");
|
|
510
|
+
fs4.mkdirSync(aflowDir, { recursive: true });
|
|
511
|
+
fs4.writeFileSync(path3.join(aflowDir, "flow.md"), STARTER_FLOW, "utf-8");
|
|
512
|
+
p.log.success(`Workflows: created ${pc.dim("~/.aflow/flow.md")} (4 starter workflows)`);
|
|
513
|
+
} else if (aflowExists) {
|
|
514
|
+
p.log.success(`Workflows: ${ecosystem.aflow.workflowCount} defined`);
|
|
515
|
+
}
|
|
516
|
+
if (doRules) {
|
|
517
|
+
const arulesDir = path3.join(home, ".arules");
|
|
518
|
+
fs4.mkdirSync(arulesDir, { recursive: true });
|
|
519
|
+
fs4.writeFileSync(path3.join(arulesDir, "rules.md"), STARTER_RULES, "utf-8");
|
|
520
|
+
p.log.success(`Guardrails: created ${pc.dim("~/.arules/rules.md")} (24 rules)`);
|
|
521
|
+
} else if (arulesExists) {
|
|
522
|
+
p.log.success(`Guardrails: ${ecosystem.arules.ruleCount} rules`);
|
|
523
|
+
}
|
|
524
|
+
if (doEval) {
|
|
525
|
+
const aevalDir = path3.join(home, ".aeval");
|
|
526
|
+
fs4.mkdirSync(aevalDir, { recursive: true });
|
|
527
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
528
|
+
fs4.writeFileSync(path3.join(aevalDir, "eval.md"), STARTER_EVAL.replace("{{DATE}}", today), "utf-8");
|
|
529
|
+
p.log.success(`Evaluation: created ${pc.dim("~/.aeval/eval.md")}`);
|
|
530
|
+
} else if (aevalExists) {
|
|
531
|
+
p.log.success("Evaluation: already configured");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
369
534
|
if (ecosystem.amem.installed) {
|
|
370
535
|
p.log.success("Memory: amem connected");
|
|
371
536
|
} else {
|
|
@@ -376,28 +541,18 @@ async function setupCommand() {
|
|
|
376
541
|
p.log.info(`Memory: built-in via core.md (upgrade with ${pc.bold("npx @aman_asmuei/amem")})`);
|
|
377
542
|
}
|
|
378
543
|
}
|
|
379
|
-
if (ecosystem.akit.installed) {
|
|
380
|
-
p.log.success(`Tools: ${ecosystem.akit.toolCount} tools configured`);
|
|
381
|
-
} else {
|
|
382
|
-
p.log.info(`Tools: run ${pc.bold("npx @aman_asmuei/akit add github")} to add capabilities`);
|
|
383
|
-
}
|
|
384
|
-
if (ecosystem.aflow.installed) {
|
|
385
|
-
p.log.success(`Workflows: ${ecosystem.aflow.workflowCount} defined`);
|
|
386
|
-
} else {
|
|
387
|
-
p.log.info(`Workflows: run ${pc.bold("npx @aman_asmuei/aflow init")} to add AI workflows`);
|
|
388
|
-
}
|
|
389
544
|
const card = [
|
|
390
545
|
"",
|
|
391
546
|
` ${pc.green("\u2714")} Your AI companion is ready.`,
|
|
392
547
|
"",
|
|
393
548
|
` ${pc.bold("aman status")} See your full ecosystem`,
|
|
394
549
|
` ${pc.bold("acore customize")} Change personality`,
|
|
395
|
-
` ${pc.bold("
|
|
550
|
+
` ${pc.bold("askill add testing")} Install skills`,
|
|
396
551
|
` ${pc.bold("npx @aman_asmuei/amem")} Enable automated memory`,
|
|
397
552
|
""
|
|
398
553
|
];
|
|
399
554
|
p.note(card.join("\n"), "");
|
|
400
|
-
p.outro(pc.dim("Identity +
|
|
555
|
+
p.outro(pc.dim("Identity + Workflows + Guardrails + Evaluation \u2014 one ecosystem."));
|
|
401
556
|
}
|
|
402
557
|
|
|
403
558
|
// src/commands/status.ts
|
|
@@ -441,6 +596,11 @@ function statusCommand() {
|
|
|
441
596
|
} else {
|
|
442
597
|
p2.log.warning(`Evaluation: not tracking \u2014 ${pc2.dim("npx @aman_asmuei/aeval init")}`);
|
|
443
598
|
}
|
|
599
|
+
if (ecosystem.askill.installed) {
|
|
600
|
+
p2.log.success(`Skills: ${ecosystem.askill.skillCount} installed \u2014 ${pc2.dim("askill list for details")}`);
|
|
601
|
+
} else {
|
|
602
|
+
p2.log.warning(`Skills: none \u2014 ${pc2.dim("npx @aman_asmuei/askill add testing")}`);
|
|
603
|
+
}
|
|
444
604
|
const platformLabels = {
|
|
445
605
|
"claude-code": "Claude Code",
|
|
446
606
|
cursor: "Cursor",
|
|
@@ -460,16 +620,225 @@ function statusCommand() {
|
|
|
460
620
|
if (ecosystem.aflow.installed) score += 1;
|
|
461
621
|
if (ecosystem.arules.installed) score += 1;
|
|
462
622
|
if (ecosystem.aeval.installed) score += 1;
|
|
463
|
-
|
|
464
|
-
const
|
|
623
|
+
if (ecosystem.askill.installed) score += 1;
|
|
624
|
+
const levels = ["Not started", "Getting started", "Growing", "Building", "Expanding", "Strong", "Advanced", "Complete ecosystem"];
|
|
625
|
+
const colors = [pc2.red, pc2.red, pc2.yellow, pc2.yellow, pc2.cyan, pc2.cyan, pc2.cyan, pc2.green];
|
|
465
626
|
p2.log.message("");
|
|
466
|
-
p2.log.info(`Ecosystem: ${colors[score](levels[score])} (${score}/
|
|
627
|
+
p2.log.info(`Ecosystem: ${colors[score](levels[score])} (${score}/7 layers active)`);
|
|
467
628
|
p2.outro("");
|
|
468
629
|
}
|
|
469
630
|
|
|
631
|
+
// src/commands/deploy.ts
|
|
632
|
+
import fs6 from "fs";
|
|
633
|
+
import path4 from "path";
|
|
634
|
+
import * as p3 from "@clack/prompts";
|
|
635
|
+
import pc3 from "picocolors";
|
|
636
|
+
async function deployCommand() {
|
|
637
|
+
p3.intro(pc3.bold("aman deploy") + pc3.dim(" \u2014 deploy your AI companion anywhere"));
|
|
638
|
+
const method = await p3.select({
|
|
639
|
+
message: "How do you want to deploy?",
|
|
640
|
+
options: [
|
|
641
|
+
{ value: "docker", label: "Docker Compose", hint: "VPS, home server, Raspberry Pi" },
|
|
642
|
+
{ value: "ollama", label: "Docker + Ollama", hint: "fully local, no API key needed" },
|
|
643
|
+
{ value: "systemd", label: "Systemd service", hint: "bare metal Linux / Raspberry Pi" },
|
|
644
|
+
{ value: "manual", label: "Show me the commands", hint: "I'll do it myself" }
|
|
645
|
+
]
|
|
646
|
+
});
|
|
647
|
+
if (p3.isCancel(method)) {
|
|
648
|
+
p3.cancel("Cancelled.");
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const cwd = process.cwd();
|
|
652
|
+
switch (method) {
|
|
653
|
+
case "docker": {
|
|
654
|
+
await deployDocker(cwd);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
case "ollama": {
|
|
658
|
+
await deployOllama(cwd);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
case "systemd": {
|
|
662
|
+
deploySystemd();
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
case "manual": {
|
|
666
|
+
deployManual();
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
p3.outro(pc3.green("Done!"));
|
|
671
|
+
}
|
|
672
|
+
async function deployDocker(cwd) {
|
|
673
|
+
const apiKey = await p3.text({
|
|
674
|
+
message: "Your LLM API key (Anthropic or OpenAI):",
|
|
675
|
+
placeholder: "sk-ant-... or sk-...",
|
|
676
|
+
validate: (v) => v.length < 10 ? "API key too short" : void 0
|
|
677
|
+
});
|
|
678
|
+
if (p3.isCancel(apiKey)) return;
|
|
679
|
+
const isAnthropic = apiKey.startsWith("sk-ant");
|
|
680
|
+
const provider = isAnthropic ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
681
|
+
const envContent = `# aman ecosystem \u2014 deployment config
|
|
682
|
+
${provider}=${apiKey}
|
|
683
|
+
AMAN_AI_NAME=Aman
|
|
684
|
+
AMAN_MODEL=${isAnthropic ? "claude-sonnet-4-6" : "gpt-4o"}
|
|
685
|
+
|
|
686
|
+
# Optional: Telegram/Discord bots
|
|
687
|
+
# TELEGRAM_BOT_TOKEN=
|
|
688
|
+
# DISCORD_BOT_TOKEN=
|
|
689
|
+
`;
|
|
690
|
+
const envPath = path4.join(cwd, ".env");
|
|
691
|
+
fs6.writeFileSync(envPath, envContent, "utf-8");
|
|
692
|
+
p3.log.success(`Created ${pc3.bold(".env")} with API key`);
|
|
693
|
+
const pkgDir = findPackageDir();
|
|
694
|
+
copyDeployFile(pkgDir, cwd, "Dockerfile");
|
|
695
|
+
copyDeployFile(pkgDir, cwd, "docker-entrypoint.sh");
|
|
696
|
+
copyDeployFile(pkgDir, cwd, "docker-compose.yml");
|
|
697
|
+
try {
|
|
698
|
+
fs6.chmodSync(path4.join(cwd, "docker-entrypoint.sh"), 493);
|
|
699
|
+
} catch {
|
|
700
|
+
}
|
|
701
|
+
p3.log.success(`Created ${pc3.bold("Dockerfile")} + ${pc3.bold("docker-compose.yml")}`);
|
|
702
|
+
p3.note(
|
|
703
|
+
`${pc3.bold("Start your companion:")}
|
|
704
|
+
|
|
705
|
+
docker compose up -d
|
|
706
|
+
|
|
707
|
+
${pc3.bold("Access:")}
|
|
708
|
+
Webhook API: http://localhost:3000/chat
|
|
709
|
+
Health check: http://localhost:3000/status
|
|
710
|
+
|
|
711
|
+
${pc3.bold("Interactive CLI:")}
|
|
712
|
+
docker compose run --rm aman-server agent
|
|
713
|
+
|
|
714
|
+
${pc3.bold("View logs:")}
|
|
715
|
+
docker compose logs -f`,
|
|
716
|
+
"Next steps"
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
async function deployOllama(cwd) {
|
|
720
|
+
const model = await p3.text({
|
|
721
|
+
message: "Ollama model to use:",
|
|
722
|
+
placeholder: "llama3.2",
|
|
723
|
+
initialValue: "llama3.2"
|
|
724
|
+
});
|
|
725
|
+
if (p3.isCancel(model)) return;
|
|
726
|
+
const envContent = `# aman ecosystem \u2014 local deployment (no API key needed)
|
|
727
|
+
AMAN_AI_NAME=Aman
|
|
728
|
+
AMAN_MODEL=${model}
|
|
729
|
+
|
|
730
|
+
# Optional: Telegram/Discord bots
|
|
731
|
+
# TELEGRAM_BOT_TOKEN=
|
|
732
|
+
# DISCORD_BOT_TOKEN=
|
|
733
|
+
`;
|
|
734
|
+
const envPath = path4.join(cwd, ".env");
|
|
735
|
+
fs6.writeFileSync(envPath, envContent, "utf-8");
|
|
736
|
+
p3.log.success(`Created ${pc3.bold(".env")} with Ollama config`);
|
|
737
|
+
const pkgDir = findPackageDir();
|
|
738
|
+
copyDeployFile(pkgDir, cwd, "Dockerfile");
|
|
739
|
+
copyDeployFile(pkgDir, cwd, "docker-entrypoint.sh");
|
|
740
|
+
copyDeployFile(pkgDir, cwd, "docker-compose.ollama.yml");
|
|
741
|
+
const src = path4.join(cwd, "docker-compose.ollama.yml");
|
|
742
|
+
const dest = path4.join(cwd, "docker-compose.yml");
|
|
743
|
+
if (fs6.existsSync(src) && !fs6.existsSync(dest)) {
|
|
744
|
+
fs6.renameSync(src, dest);
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
fs6.chmodSync(path4.join(cwd, "docker-entrypoint.sh"), 493);
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
p3.log.success(`Created ${pc3.bold("Dockerfile")} + ${pc3.bold("docker-compose.yml")} (with Ollama)`);
|
|
751
|
+
p3.note(
|
|
752
|
+
`${pc3.bold("Start your companion (fully local):")}
|
|
753
|
+
|
|
754
|
+
docker compose up -d
|
|
755
|
+
|
|
756
|
+
First run downloads ${model} (~2GB). After that it's instant.
|
|
757
|
+
|
|
758
|
+
${pc3.bold("Access:")}
|
|
759
|
+
Webhook API: http://localhost:3000/chat
|
|
760
|
+
Ollama: http://localhost:11434
|
|
761
|
+
|
|
762
|
+
${pc3.bold("Works on:")}
|
|
763
|
+
Raspberry Pi 4/5 (ARM64), any Linux, macOS, Windows`,
|
|
764
|
+
"Next steps"
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
function deploySystemd() {
|
|
768
|
+
p3.note(
|
|
769
|
+
`${pc3.bold("1. Install Node.js 20+:")}
|
|
770
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
|
|
771
|
+
sudo apt install -y nodejs
|
|
772
|
+
|
|
773
|
+
${pc3.bold("2. Create aman user:")}
|
|
774
|
+
sudo useradd -m -s /bin/bash aman
|
|
775
|
+
sudo -u aman npm install -g @aman_asmuei/achannel @aman_asmuei/aman-mcp
|
|
776
|
+
|
|
777
|
+
${pc3.bold("3. Configure API key:")}
|
|
778
|
+
sudo mkdir -p /home/aman/.aman-agent
|
|
779
|
+
echo 'ANTHROPIC_API_KEY=sk-ant-...' | sudo tee /home/aman/.aman-agent/env
|
|
780
|
+
|
|
781
|
+
${pc3.bold("4. Install service:")}
|
|
782
|
+
sudo cp aman.service /etc/systemd/system/
|
|
783
|
+
sudo systemctl daemon-reload
|
|
784
|
+
sudo systemctl enable --now aman
|
|
785
|
+
|
|
786
|
+
${pc3.bold("5. Check status:")}
|
|
787
|
+
sudo systemctl status aman
|
|
788
|
+
sudo journalctl -u aman -f`,
|
|
789
|
+
"Systemd deployment (Raspberry Pi / bare metal)"
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
function deployManual() {
|
|
793
|
+
p3.note(
|
|
794
|
+
`${pc3.bold("Docker (one command):")}
|
|
795
|
+
docker run -d -p 3000:3000 \\
|
|
796
|
+
-e ANTHROPIC_API_KEY=sk-ant-... \\
|
|
797
|
+
-v aman-data:/root/.acore \\
|
|
798
|
+
-v aman-memory:/root/.amem \\
|
|
799
|
+
ghcr.io/amanasmuei/aman serve
|
|
800
|
+
|
|
801
|
+
${pc3.bold("Docker + Ollama (fully local):")}
|
|
802
|
+
docker run -d --name ollama ollama/ollama
|
|
803
|
+
docker exec ollama ollama pull llama3.2
|
|
804
|
+
docker run -d -p 3000:3000 \\
|
|
805
|
+
--link ollama \\
|
|
806
|
+
-e OLLAMA_HOST=http://ollama:11434 \\
|
|
807
|
+
ghcr.io/amanasmuei/aman serve
|
|
808
|
+
|
|
809
|
+
${pc3.bold("npm (any server):")}
|
|
810
|
+
npm install -g @aman_asmuei/achannel @aman_asmuei/aman-mcp
|
|
811
|
+
ANTHROPIC_API_KEY=sk-ant-... achannel serve
|
|
812
|
+
|
|
813
|
+
${pc3.bold("Raspberry Pi:")}
|
|
814
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
|
|
815
|
+
sudo apt install -y nodejs
|
|
816
|
+
npm install -g @aman_asmuei/achannel @aman_asmuei/aman-mcp
|
|
817
|
+
ANTHROPIC_API_KEY=sk-ant-... achannel serve`,
|
|
818
|
+
"Manual deployment commands"
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
function findPackageDir() {
|
|
822
|
+
let dir = new URL(".", import.meta.url).pathname;
|
|
823
|
+
for (let i = 0; i < 5; i++) {
|
|
824
|
+
if (fs6.existsSync(path4.join(dir, "Dockerfile"))) return dir;
|
|
825
|
+
dir = path4.dirname(dir);
|
|
826
|
+
}
|
|
827
|
+
const globalDir = path4.join(process.env.npm_config_prefix || "/usr/local", "lib/node_modules/@aman_asmuei/aman");
|
|
828
|
+
if (fs6.existsSync(path4.join(globalDir, "Dockerfile"))) return globalDir;
|
|
829
|
+
return process.cwd();
|
|
830
|
+
}
|
|
831
|
+
function copyDeployFile(pkgDir, destDir, filename) {
|
|
832
|
+
const src = path4.join(pkgDir, filename);
|
|
833
|
+
const dest = path4.join(destDir, filename);
|
|
834
|
+
if (fs6.existsSync(src)) {
|
|
835
|
+
fs6.copyFileSync(src, dest);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
470
839
|
// src/index.ts
|
|
471
840
|
var program = new Command();
|
|
472
|
-
program.name("aman").description("Your complete AI companion \u2014 identity, memory, and tools in one command").version("0.
|
|
841
|
+
program.name("aman").description("Your complete AI companion \u2014 identity, memory, and tools in one command").version("0.3.0").action(() => {
|
|
473
842
|
const ecosystem = detectEcosystem();
|
|
474
843
|
if (ecosystem.acore.installed) {
|
|
475
844
|
statusCommand();
|
|
@@ -479,4 +848,5 @@ program.name("aman").description("Your complete AI companion \u2014 identity, me
|
|
|
479
848
|
});
|
|
480
849
|
program.command("setup").description("Set up your AI companion (identity + memory + tools)").action(() => setupCommand());
|
|
481
850
|
program.command("status").description("View your full ecosystem status").action(() => statusCommand());
|
|
851
|
+
program.command("deploy").description("Deploy your AI companion (Docker, systemd, or cloud)").action(() => deployCommand());
|
|
482
852
|
program.parse();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# aman ecosystem — fully local with Ollama (no API key needed)
|
|
2
|
+
# Usage: docker compose -f docker-compose.ollama.yml up -d
|
|
3
|
+
#
|
|
4
|
+
# First run pulls llama3.2 model (~2GB download)
|
|
5
|
+
# Works on: Raspberry Pi 4/5 (ARM64), any Linux/Mac with Docker
|
|
6
|
+
|
|
7
|
+
services:
|
|
8
|
+
ollama:
|
|
9
|
+
image: ollama/ollama:latest
|
|
10
|
+
restart: unless-stopped
|
|
11
|
+
volumes:
|
|
12
|
+
- ollama-models:/root/.ollama
|
|
13
|
+
ports:
|
|
14
|
+
- "11434:11434"
|
|
15
|
+
# Uncomment for GPU support:
|
|
16
|
+
# deploy:
|
|
17
|
+
# resources:
|
|
18
|
+
# reservations:
|
|
19
|
+
# devices:
|
|
20
|
+
# - capabilities: [gpu]
|
|
21
|
+
|
|
22
|
+
ollama-init:
|
|
23
|
+
image: ollama/ollama:latest
|
|
24
|
+
depends_on:
|
|
25
|
+
- ollama
|
|
26
|
+
restart: "no"
|
|
27
|
+
entrypoint: >
|
|
28
|
+
sh -c "sleep 5 && ollama pull llama3.2"
|
|
29
|
+
environment:
|
|
30
|
+
- OLLAMA_HOST=http://ollama:11434
|
|
31
|
+
|
|
32
|
+
aman-server:
|
|
33
|
+
build: .
|
|
34
|
+
command: serve
|
|
35
|
+
restart: unless-stopped
|
|
36
|
+
depends_on:
|
|
37
|
+
- ollama
|
|
38
|
+
ports:
|
|
39
|
+
- "3000:3000"
|
|
40
|
+
environment:
|
|
41
|
+
- OLLAMA_HOST=http://ollama:11434
|
|
42
|
+
- AMAN_AI_NAME=${AMAN_AI_NAME:-Aman}
|
|
43
|
+
- AMAN_MODEL=${AMAN_MODEL:-llama3.2}
|
|
44
|
+
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
|
|
45
|
+
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:-}
|
|
46
|
+
volumes:
|
|
47
|
+
- aman-identity:/root/.acore
|
|
48
|
+
- aman-memory:/root/.amem
|
|
49
|
+
- aman-config:/root/.aman-agent
|
|
50
|
+
- aman-rules:/root/.arules
|
|
51
|
+
- aman-workflows:/root/.aflow
|
|
52
|
+
- aman-eval:/root/.aeval
|
|
53
|
+
- aman-skills:/root/.askill
|
|
54
|
+
- aman-tools:/root/.akit
|
|
55
|
+
|
|
56
|
+
volumes:
|
|
57
|
+
ollama-models:
|
|
58
|
+
aman-identity:
|
|
59
|
+
aman-memory:
|
|
60
|
+
aman-config:
|
|
61
|
+
aman-rules:
|
|
62
|
+
aman-workflows:
|
|
63
|
+
aman-eval:
|
|
64
|
+
aman-skills:
|
|
65
|
+
aman-tools:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# aman ecosystem — full deployment
|
|
2
|
+
# Usage: docker compose up -d
|
|
3
|
+
#
|
|
4
|
+
# Modes:
|
|
5
|
+
# aman-server → always-on webhook/Telegram/Discord server
|
|
6
|
+
# aman-agent → interactive CLI (attach with: docker attach aman-agent)
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
aman-server:
|
|
10
|
+
build: .
|
|
11
|
+
command: serve
|
|
12
|
+
restart: unless-stopped
|
|
13
|
+
ports:
|
|
14
|
+
- "3000:3000"
|
|
15
|
+
environment:
|
|
16
|
+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
|
17
|
+
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
18
|
+
- AMAN_AI_NAME=${AMAN_AI_NAME:-Aman}
|
|
19
|
+
- AMAN_MODEL=${AMAN_MODEL:-claude-sonnet-4-6}
|
|
20
|
+
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
|
|
21
|
+
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:-}
|
|
22
|
+
volumes:
|
|
23
|
+
- aman-identity:/root/.acore
|
|
24
|
+
- aman-memory:/root/.amem
|
|
25
|
+
- aman-config:/root/.aman-agent
|
|
26
|
+
- aman-rules:/root/.arules
|
|
27
|
+
- aman-workflows:/root/.aflow
|
|
28
|
+
- aman-eval:/root/.aeval
|
|
29
|
+
- aman-skills:/root/.askill
|
|
30
|
+
- aman-tools:/root/.akit
|
|
31
|
+
|
|
32
|
+
volumes:
|
|
33
|
+
aman-identity:
|
|
34
|
+
aman-memory:
|
|
35
|
+
aman-config:
|
|
36
|
+
aman-rules:
|
|
37
|
+
aman-workflows:
|
|
38
|
+
aman-eval:
|
|
39
|
+
aman-skills:
|
|
40
|
+
aman-tools:
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# aman ecosystem — Docker entrypoint
|
|
5
|
+
# Modes:
|
|
6
|
+
# agent → interactive CLI (default)
|
|
7
|
+
# serve → achannel webhook/Telegram/Discord server
|
|
8
|
+
# setup → run ecosystem setup
|
|
9
|
+
# status → show ecosystem status
|
|
10
|
+
|
|
11
|
+
MODE="${1:-agent}"
|
|
12
|
+
|
|
13
|
+
# Auto-create minimal identity if none exists
|
|
14
|
+
if [ ! -f /root/.acore/core.md ]; then
|
|
15
|
+
echo " First run — creating default identity..."
|
|
16
|
+
AI_NAME="${AMAN_AI_NAME:-Aman}"
|
|
17
|
+
cat > /root/.acore/core.md << EOF
|
|
18
|
+
# ${AI_NAME}
|
|
19
|
+
|
|
20
|
+
## Identity
|
|
21
|
+
- Role: ${AI_NAME} is your AI companion
|
|
22
|
+
- Personality: helpful, direct, adaptive
|
|
23
|
+
- Communication: clear and concise
|
|
24
|
+
- Values: honesty, simplicity, understanding
|
|
25
|
+
- Boundaries: won't pretend to be human
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Relationship
|
|
30
|
+
- Name: [user]
|
|
31
|
+
- Role: [role]
|
|
32
|
+
- Nicknames: []
|
|
33
|
+
- Communication: [updated over time]
|
|
34
|
+
- Detail level: balanced
|
|
35
|
+
- Domain: [detected from project]
|
|
36
|
+
- Personal: []
|
|
37
|
+
- Learned patterns: []
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Session
|
|
42
|
+
- Last updated: $(date +%Y-%m-%d)
|
|
43
|
+
- Resume: [starting fresh]
|
|
44
|
+
- Active topics: []
|
|
45
|
+
- Recent decisions: []
|
|
46
|
+
- Temp notes: []
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Dynamics
|
|
51
|
+
|
|
52
|
+
### Trust & Rapport
|
|
53
|
+
- Level: 3
|
|
54
|
+
- Trajectory: building
|
|
55
|
+
- Evidence: []
|
|
56
|
+
- Unlocked behaviors: []
|
|
57
|
+
|
|
58
|
+
### Emotional Patterns
|
|
59
|
+
- Baseline energy: steady
|
|
60
|
+
- Stress signals: []
|
|
61
|
+
- Support style: problem-solve
|
|
62
|
+
- Current read: fresh start
|
|
63
|
+
|
|
64
|
+
### Conflict & Repair
|
|
65
|
+
- History: []
|
|
66
|
+
- Conflict style: direct
|
|
67
|
+
- Learned response: []
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Context Modes
|
|
72
|
+
|
|
73
|
+
> Active mode inferred from conversation context.
|
|
74
|
+
|
|
75
|
+
### Default
|
|
76
|
+
- Tone: casual-professional
|
|
77
|
+
- Detail: balanced
|
|
78
|
+
- Initiative: proactive
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Memory Lifecycle
|
|
83
|
+
|
|
84
|
+
### Importance
|
|
85
|
+
- Critical: [user boundaries, core preferences]
|
|
86
|
+
- Persistent: [projects, tech stack]
|
|
87
|
+
- Ephemeral: [temporary context]
|
|
88
|
+
|
|
89
|
+
### Size
|
|
90
|
+
- Target: under 2000 tokens
|
|
91
|
+
EOF
|
|
92
|
+
echo " Identity created: ${AI_NAME}"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Auto-create aman-agent config if API key is provided
|
|
96
|
+
if [ ! -f /root/.aman-agent/config.json ]; then
|
|
97
|
+
if [ -n "$ANTHROPIC_API_KEY" ]; then
|
|
98
|
+
MODEL="${AMAN_MODEL:-claude-sonnet-4-6}"
|
|
99
|
+
cat > /root/.aman-agent/config.json << EOF
|
|
100
|
+
{
|
|
101
|
+
"provider": "anthropic",
|
|
102
|
+
"apiKey": "${ANTHROPIC_API_KEY}",
|
|
103
|
+
"model": "${MODEL}",
|
|
104
|
+
"hooks": {
|
|
105
|
+
"memoryRecall": true,
|
|
106
|
+
"sessionResume": true,
|
|
107
|
+
"rulesCheck": true,
|
|
108
|
+
"workflowSuggest": true,
|
|
109
|
+
"evalPrompt": false,
|
|
110
|
+
"autoSessionSave": true,
|
|
111
|
+
"extractMemories": true,
|
|
112
|
+
"featureHints": true,
|
|
113
|
+
"personalityAdapt": true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
EOF
|
|
117
|
+
echo " Config created: Anthropic / ${MODEL}"
|
|
118
|
+
elif [ -n "$OPENAI_API_KEY" ]; then
|
|
119
|
+
MODEL="${AMAN_MODEL:-gpt-4o}"
|
|
120
|
+
cat > /root/.aman-agent/config.json << EOF
|
|
121
|
+
{
|
|
122
|
+
"provider": "openai",
|
|
123
|
+
"apiKey": "${OPENAI_API_KEY}",
|
|
124
|
+
"model": "${MODEL}",
|
|
125
|
+
"hooks": {
|
|
126
|
+
"memoryRecall": true,
|
|
127
|
+
"sessionResume": true,
|
|
128
|
+
"rulesCheck": true,
|
|
129
|
+
"workflowSuggest": true,
|
|
130
|
+
"evalPrompt": false,
|
|
131
|
+
"autoSessionSave": true,
|
|
132
|
+
"extractMemories": true,
|
|
133
|
+
"featureHints": true,
|
|
134
|
+
"personalityAdapt": true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
EOF
|
|
138
|
+
echo " Config created: OpenAI / ${MODEL}"
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
case "$MODE" in
|
|
143
|
+
agent)
|
|
144
|
+
echo " Starting aman-agent (interactive)..."
|
|
145
|
+
exec aman-agent
|
|
146
|
+
;;
|
|
147
|
+
serve)
|
|
148
|
+
echo " Starting achannel server on :3000..."
|
|
149
|
+
exec achannel serve
|
|
150
|
+
;;
|
|
151
|
+
setup)
|
|
152
|
+
exec aman setup
|
|
153
|
+
;;
|
|
154
|
+
status)
|
|
155
|
+
exec aman status
|
|
156
|
+
;;
|
|
157
|
+
sh|bash)
|
|
158
|
+
exec /bin/sh
|
|
159
|
+
;;
|
|
160
|
+
*)
|
|
161
|
+
echo "Unknown mode: $MODE"
|
|
162
|
+
echo "Usage: docker run aman [agent|serve|setup|status|sh]"
|
|
163
|
+
exit 1
|
|
164
|
+
;;
|
|
165
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aman_asmuei/aman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Your complete AI companion — identity, memory, and tools in one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"bin",
|
|
13
|
-
"template"
|
|
13
|
+
"template",
|
|
14
|
+
"deploy",
|
|
15
|
+
"Dockerfile",
|
|
16
|
+
"docker-entrypoint.sh",
|
|
17
|
+
"docker-compose.yml",
|
|
18
|
+
"docker-compose.ollama.yml"
|
|
14
19
|
],
|
|
15
20
|
"scripts": {
|
|
16
21
|
"build": "tsup",
|