@aman_asmuei/aman-agent 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1939,6 +1939,19 @@ var DEFAULT_HOOKS = {
1939
1939
  featureHints: true,
1940
1940
  personalityAdapt: true
1941
1941
  };
1942
+ var DEFAULT_MIRROR = {
1943
+ enabled: true,
1944
+ dir: "",
1945
+ // populated in loadConfig via homeDir() — empty string is a sentinel
1946
+ tiers: ["core", "working", "archival"],
1947
+ autoSyncOnStartup: true
1948
+ };
1949
+ function expandHome(p5) {
1950
+ if (p5.startsWith("~/") || p5 === "~") {
1951
+ return path.join(os.homedir(), p5.slice(1));
1952
+ }
1953
+ return p5;
1954
+ }
1942
1955
  function homeDir() {
1943
1956
  return process.env.AMAN_HOME || process.env.AMAN_AGENT_HOME || path.join(os.homedir(), ".aman-agent");
1944
1957
  }
@@ -1969,6 +1982,12 @@ function loadConfig() {
1969
1982
  try {
1970
1983
  const raw = JSON.parse(fs.readFileSync(p5, "utf-8"));
1971
1984
  raw.hooks = { ...DEFAULT_HOOKS, ...raw.hooks };
1985
+ raw.mirror = {
1986
+ ...DEFAULT_MIRROR,
1987
+ dir: path.join(homeDir(), "memories"),
1988
+ ...raw.mirror ?? {}
1989
+ };
1990
+ raw.mirror.dir = expandHome(raw.mirror.dir);
1972
1991
  return raw;
1973
1992
  } catch {
1974
1993
  return null;
@@ -3386,12 +3405,15 @@ import {
3386
3405
  syncFromClaude,
3387
3406
  exportForTeam,
3388
3407
  importFromTeam,
3389
- syncToCopilot
3408
+ syncToCopilot,
3409
+ MirrorEngine,
3410
+ parseFrontmatter
3390
3411
  } from "@aman_asmuei/amem-core";
3391
3412
  import path7 from "path";
3392
3413
  import os6 from "os";
3393
3414
  import fs7 from "fs";
3394
3415
  var db = null;
3416
+ var mirrorEngine = null;
3395
3417
  var currentProject = "global";
3396
3418
  async function initMemory(project) {
3397
3419
  if (db) return db;
@@ -3420,6 +3442,23 @@ async function initMemory(project) {
3420
3442
  }
3421
3443
  }
3422
3444
  currentProject = project ?? "global";
3445
+ try {
3446
+ const agentCfg = loadConfig();
3447
+ const mirrorCfg = agentCfg?.mirror;
3448
+ const enabled = mirrorCfg?.enabled ?? true;
3449
+ if (enabled) {
3450
+ const dir = expandHome(mirrorCfg?.dir ?? path7.join(homeDir(), "memories"));
3451
+ const tiers = mirrorCfg?.tiers ?? ["core", "working", "archival"];
3452
+ mirrorEngine = new MirrorEngine(db, {
3453
+ dir,
3454
+ tiers,
3455
+ includeIndex: true
3456
+ });
3457
+ }
3458
+ } catch (err) {
3459
+ console.warn(`[amem-mirror] construction failed: ${err instanceof Error ? err.message : String(err)}`);
3460
+ mirrorEngine = null;
3461
+ }
3423
3462
  preloadEmbeddings();
3424
3463
  setTimeout(() => {
3425
3464
  try {
@@ -3449,7 +3488,12 @@ async function memoryContext(topic, maxTokens) {
3449
3488
  return buildContext(getDb(), topic, { maxTokens, scope: currentProject });
3450
3489
  }
3451
3490
  async function memoryStore(opts) {
3452
- return storeMemory(getDb(), opts);
3491
+ const result = await storeMemory(getDb(), opts);
3492
+ if (result.action !== "private" && mirrorEngine) {
3493
+ const saved = getDb().getById(result.id);
3494
+ if (saved) void mirrorEngine.onSave(saved);
3495
+ }
3496
+ return result;
3453
3497
  }
3454
3498
  function memoryLog(sessionId, role, content) {
3455
3499
  return getDb().appendLog({
@@ -3473,6 +3517,7 @@ async function memoryForget(opts) {
3473
3517
  db2.deleteMemory(fullId);
3474
3518
  const vecIdx = getVectorIndex();
3475
3519
  if (vecIdx) vecIdx.remove(fullId);
3520
+ void mirrorEngine?.onDelete(fullId, memory.type);
3476
3521
  return { deleted: 1, message: `Deleted: "${memory.content}" (${memory.type})` };
3477
3522
  }
3478
3523
  if (opts.type) {
@@ -3483,6 +3528,7 @@ async function memoryForget(opts) {
3483
3528
  for (const m of matches) {
3484
3529
  db2.deleteMemory(m.id);
3485
3530
  if (vecIdx) vecIdx.remove(m.id);
3531
+ void mirrorEngine?.onDelete(m.id, m.type);
3486
3532
  }
3487
3533
  return { deleted: matches.length, message: `Deleted ${matches.length} "${opts.type}" memories.` };
3488
3534
  }
@@ -3494,6 +3540,7 @@ async function memoryForget(opts) {
3494
3540
  for (const m of matches) {
3495
3541
  db2.deleteMemory(m.id);
3496
3542
  if (vecIdx) vecIdx.remove(m.id);
3543
+ void mirrorEngine?.onDelete(m.id, m.type);
3497
3544
  }
3498
3545
  return { deleted: matches.length, message: `Deleted ${matches.length} memories matching "${opts.query}".` };
3499
3546
  }
@@ -3649,6 +3696,140 @@ async function memorySync(action, opts = {}) {
3649
3696
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
3650
3697
  }
3651
3698
  }
3699
+ function getMirrorEngine() {
3700
+ return mirrorEngine;
3701
+ }
3702
+ function extractAmemFields(raw) {
3703
+ const out = {};
3704
+ const block = raw.match(/^---\s*\n([\s\S]*?)\n---/);
3705
+ if (!block) return out;
3706
+ for (const line of block[1].split("\n")) {
3707
+ const kv = line.match(/^(amem_\w+)\s*:\s*(.+)$/);
3708
+ if (!kv) continue;
3709
+ const key = kv[1];
3710
+ const val = kv[2].trim();
3711
+ switch (key) {
3712
+ case "amem_id":
3713
+ out.amemId = val;
3714
+ break;
3715
+ case "amem_type":
3716
+ out.amemType = val;
3717
+ break;
3718
+ case "amem_tier":
3719
+ out.amemTier = val;
3720
+ break;
3721
+ case "amem_confidence": {
3722
+ const n = Number(val);
3723
+ if (Number.isFinite(n)) out.amemConfidence = n;
3724
+ break;
3725
+ }
3726
+ case "amem_tags":
3727
+ out.amemTags = val.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
3728
+ break;
3729
+ case "amem_created": {
3730
+ const t = Date.parse(val);
3731
+ if (!Number.isNaN(t)) out.amemCreated = t;
3732
+ break;
3733
+ }
3734
+ }
3735
+ }
3736
+ return out;
3737
+ }
3738
+ var CLAUDE_TO_AMEM_TYPE = {
3739
+ feedback: "correction",
3740
+ project: "decision",
3741
+ user: "preference",
3742
+ reference: "topology"
3743
+ };
3744
+ async function startupAutoSync() {
3745
+ const cfg = loadConfig();
3746
+ const autoSync = cfg?.mirror?.autoSyncOnStartup ?? true;
3747
+ const enabled = cfg?.mirror?.enabled ?? true;
3748
+ if (!enabled || !autoSync) return null;
3749
+ const engine = mirrorEngine;
3750
+ if (!engine) return null;
3751
+ const dir = engine.status().dir;
3752
+ try {
3753
+ return await syncFromMirrorDir(dir);
3754
+ } catch {
3755
+ return null;
3756
+ }
3757
+ }
3758
+ async function syncFromMirrorDir(dir) {
3759
+ const db2 = getDb();
3760
+ const result = {
3761
+ imported: 0,
3762
+ skipped: 0,
3763
+ updated: 0,
3764
+ details: [],
3765
+ projectsScanned: 1
3766
+ };
3767
+ if (!fs7.existsSync(dir)) return result;
3768
+ const targets = [];
3769
+ const walk = (d) => {
3770
+ for (const name of fs7.readdirSync(d)) {
3771
+ if (name === "INDEX.md") continue;
3772
+ const full = path7.join(d, name);
3773
+ const stat = fs7.statSync(full);
3774
+ if (stat.isDirectory()) walk(full);
3775
+ else if (name.endsWith(".md")) targets.push(full);
3776
+ }
3777
+ };
3778
+ try {
3779
+ walk(dir);
3780
+ } catch {
3781
+ return result;
3782
+ }
3783
+ for (const filePath of targets) {
3784
+ const raw = fs7.readFileSync(filePath, "utf-8");
3785
+ const parsed = parseFrontmatter(raw, filePath);
3786
+ if (!parsed) {
3787
+ result.skipped++;
3788
+ result.details.push({ action: "skipped", name: filePath, type: "unknown", reason: "invalid frontmatter" });
3789
+ continue;
3790
+ }
3791
+ const amem = extractAmemFields(raw);
3792
+ const resolvedType = amem.amemType ?? CLAUDE_TO_AMEM_TYPE[parsed.type];
3793
+ if (!resolvedType) {
3794
+ result.skipped++;
3795
+ result.details.push({ action: "skipped", name: parsed.name, type: parsed.type, reason: `Unknown type: ${parsed.type}` });
3796
+ continue;
3797
+ }
3798
+ const content = parsed.body;
3799
+ if (!content.trim()) {
3800
+ result.skipped++;
3801
+ result.details.push({ action: "skipped", name: parsed.name, type: resolvedType, reason: "empty body" });
3802
+ continue;
3803
+ }
3804
+ const existing = db2.findByContentHash(content);
3805
+ if (existing) {
3806
+ result.skipped++;
3807
+ result.details.push({ action: "skipped", name: parsed.name, type: resolvedType, reason: "duplicate content" });
3808
+ continue;
3809
+ }
3810
+ const embedding = await generateEmbedding(content);
3811
+ const confidence = amem.amemConfidence ?? 0.8;
3812
+ const isGlobal = resolvedType === "preference" || resolvedType === "correction";
3813
+ const scope = isGlobal ? "global" : currentProject;
3814
+ const tags = amem.amemTags ?? ["mirror-sync"];
3815
+ db2.insertMemory({
3816
+ content,
3817
+ type: resolvedType,
3818
+ tags,
3819
+ confidence,
3820
+ source: "mirror-sync",
3821
+ embedding,
3822
+ scope,
3823
+ ...amem.amemTier ? { tier: amem.amemTier } : {},
3824
+ // Preserve original creation time when the mirror file carries it,
3825
+ // so round-tripped memories keep their chronology for recency decay.
3826
+ ...amem.amemCreated ? { validFrom: amem.amemCreated } : {}
3827
+ });
3828
+ result.imported++;
3829
+ result.details.push({ action: "imported", name: parsed.name, type: resolvedType });
3830
+ }
3831
+ return result;
3832
+ }
3652
3833
 
3653
3834
  // src/profile-templates.ts
3654
3835
  import fs8 from "fs";
@@ -8628,7 +8809,7 @@ async function handleMemoryCommand(action, args, ctx) {
8628
8809
  return { handled: true, output: pc6.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
8629
8810
  }
8630
8811
  }
8631
- if (action && !["search", "clear", "timeline", "stats", "export", "since", "fts", "help", "doctor", "repair", "config", "reflect", "consolidate", "tier", "detail", "relate", "expire", "versions", "sync"].includes(action)) {
8812
+ if (action && !["search", "clear", "timeline", "stats", "export", "since", "fts", "help", "doctor", "repair", "config", "reflect", "consolidate", "tier", "detail", "relate", "expire", "versions", "sync", "mirror"].includes(action)) {
8632
8813
  try {
8633
8814
  const topic = [action, ...args].join(" ");
8634
8815
  const result = await memoryContext(topic);
@@ -8739,6 +8920,24 @@ async function handleMemoryCommand(action, args, ctx) {
8739
8920
  }
8740
8921
  if (action === "export") {
8741
8922
  try {
8923
+ const toDir = parseFlagValue(args, "--to");
8924
+ if (toDir !== void 0) {
8925
+ const engine = getMirrorEngine();
8926
+ if (!engine) {
8927
+ return { handled: true, output: pc6.yellow("Mirror is disabled \u2014 enable via config.mirror.enabled in config.json.") };
8928
+ }
8929
+ const resolved = expandHome(toDir);
8930
+ const res = await engine.exportSnapshot(resolved);
8931
+ const lines2 = [
8932
+ `Wrote ${res.written} files to ${resolved} (${res.skipped} skipped, ${res.errors.length} errors).`
8933
+ ];
8934
+ if (res.errors.length > 0) {
8935
+ lines2.push("");
8936
+ for (const e of res.errors.slice(0, 5)) lines2.push(` - ${e}`);
8937
+ if (res.errors.length > 5) lines2.push(` ...and ${res.errors.length - 5} more`);
8938
+ }
8939
+ return { handled: true, output: lines2.join("\n") };
8940
+ }
8742
8941
  const format = args[0] === "json" ? "json" : "markdown";
8743
8942
  const memories = memoryExport();
8744
8943
  if (memories.length === 0) {
@@ -8819,12 +9018,16 @@ async function handleMemoryCommand(action, args, ctx) {
8819
9018
  ` ${pc6.cyan("/memory since")} <Nh|Nd|Nw> Memories from time window`,
8820
9019
  ` ${pc6.cyan("/memory stats")} Show memory statistics`,
8821
9020
  ` ${pc6.cyan("/memory export")} [json] Export all memories`,
9021
+ ` ${pc6.cyan("/memory export --to")} <dir> Snapshot mirror-format files to <dir>`,
8822
9022
  ` ${pc6.cyan("/memory timeline")} View memory timeline`,
8823
9023
  ` ${pc6.cyan("/memory clear")} <query> Delete matching memories`,
8824
9024
  ` ${pc6.cyan("/memory clear --type")} <type> Delete all of a type`,
8825
9025
  ` ${pc6.cyan("/memory doctor")} Run memory diagnostics`,
8826
9026
  ` ${pc6.cyan("/memory repair")} Dry-run repair (safe)`,
8827
- ` ${pc6.cyan("/memory config")} [key=value] View or update config (e.g. consolidation.maxStaleDays=60)`
9027
+ ` ${pc6.cyan("/memory config")} [key=value] View or update config (e.g. consolidation.maxStaleDays=60)`,
9028
+ ` ${pc6.cyan("/memory mirror status")} Show mirror dir, file count, health`,
9029
+ ` ${pc6.cyan("/memory mirror rebuild")} Rebuild the mirror from the DB`,
9030
+ ` ${pc6.cyan("/memory sync --from")} <dir> Import edits from a mirror-format dir`
8828
9031
  ].join("\n") };
8829
9032
  }
8830
9033
  if (action === "doctor") {
@@ -9009,7 +9212,51 @@ async function handleMemoryCommand(action, args, ctx) {
9009
9212
  }
9010
9213
  return { handled: true, output: lines.join("\n") };
9011
9214
  }
9215
+ if (action === "mirror") {
9216
+ const sub = args[0];
9217
+ try {
9218
+ if (sub === "status") {
9219
+ const engine = getMirrorEngine();
9220
+ if (!engine) return { handled: true, output: pc6.yellow("Mirror is disabled \u2014 enable via config.mirror.enabled in config.json.") };
9221
+ const s = engine.status();
9222
+ const last = s.lastWriteAt ? `${new Date(s.lastWriteAt).toISOString()} (${relativeTimeFromNow(s.lastWriteAt)})` : "never";
9223
+ const lines = [
9224
+ pc6.bold("Mirror:"),
9225
+ ` Dir: ${s.dir}`,
9226
+ ` File count: ${s.fileCount}`,
9227
+ ` Last write: ${last}`,
9228
+ ` Health: ${s.healthy ? "healthy" : "drifted"}`
9229
+ ];
9230
+ return { handled: true, output: lines.join("\n") };
9231
+ }
9232
+ if (sub === "rebuild") {
9233
+ const engine = getMirrorEngine();
9234
+ if (!engine) return { handled: true, output: pc6.yellow("Mirror is disabled \u2014 enable via config.mirror.enabled in config.json.") };
9235
+ const res = await engine.fullMirror();
9236
+ return {
9237
+ handled: true,
9238
+ output: `Rebuilt mirror: ${res.written} files written, ${res.skipped} skipped, ${res.errors.length} errors.`
9239
+ };
9240
+ }
9241
+ return { handled: true, output: pc6.yellow("Unknown mirror subcommand; try 'status' or 'rebuild'.") };
9242
+ } catch (err) {
9243
+ return { handled: true, output: pc6.red(`Mirror error: ${err instanceof Error ? err.message : String(err)}`) };
9244
+ }
9245
+ }
9012
9246
  if (action === "sync") {
9247
+ const fromDir = parseFlagValue(args, "--from");
9248
+ if (fromDir !== void 0) {
9249
+ try {
9250
+ const resolved = expandHome(fromDir);
9251
+ const res = await syncFromMirrorDir(resolved);
9252
+ return {
9253
+ handled: true,
9254
+ output: `Synced ${res.imported} memories from ${resolved} (${res.skipped} skipped, ${res.updated} updated).`
9255
+ };
9256
+ } catch (err) {
9257
+ return { handled: true, output: pc6.red(`Sync error: ${err instanceof Error ? err.message : String(err)}`) };
9258
+ }
9259
+ }
9013
9260
  const syncAction = args[0];
9014
9261
  if (!syncAction) {
9015
9262
  return { handled: true, output: pc6.yellow("Usage: /memory sync <import-claude|export-team|import-team|sync-copilot>") };
@@ -9031,6 +9278,25 @@ ${JSON.stringify(result, null, 2)}` };
9031
9278
  }
9032
9279
  return { handled: true, output: pc6.yellow(`Unknown action: /memory ${action}. Try /memory --help`) };
9033
9280
  }
9281
+ function parseFlagValue(args, flag) {
9282
+ for (let i = 0; i < args.length; i++) {
9283
+ const a = args[i];
9284
+ if (a === flag) {
9285
+ return args[i + 1];
9286
+ }
9287
+ if (a.startsWith(`${flag}=`)) {
9288
+ return a.slice(flag.length + 1);
9289
+ }
9290
+ }
9291
+ return void 0;
9292
+ }
9293
+ function relativeTimeFromNow(ts) {
9294
+ const delta = Date.now() - ts;
9295
+ if (delta < 6e4) return "just now";
9296
+ if (delta < 36e5) return `${Math.round(delta / 6e4)}m ago`;
9297
+ if (delta < 864e5) return `${Math.round(delta / 36e5)}h ago`;
9298
+ return `${Math.round(delta / 864e5)}d ago`;
9299
+ }
9034
9300
  function handleStatusCommand(ctx) {
9035
9301
  const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
9036
9302
  const amemConnected = isMemoryInitialized();
@@ -9188,7 +9454,7 @@ function handleReset(action) {
9188
9454
  function handleUpdate() {
9189
9455
  try {
9190
9456
  const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
9191
- const local = true ? "0.41.0" : "unknown";
9457
+ const local = true ? "0.42.0" : "unknown";
9192
9458
  if (current === local) {
9193
9459
  return { handled: true, output: `${pc6.green("Up to date")} \u2014 v${local}` };
9194
9460
  }
@@ -12342,7 +12608,7 @@ var Inbox = class {
12342
12608
  // package.json
12343
12609
  var package_default = {
12344
12610
  name: "@aman_asmuei/aman-agent",
12345
- version: "0.41.0",
12611
+ version: "0.42.0",
12346
12612
  description: "Your AI companion, running locally \u2014 powered by the aman ecosystem",
12347
12613
  type: "module",
12348
12614
  engines: {
@@ -12365,10 +12631,10 @@ var package_default = {
12365
12631
  prepublishOnly: "npm run build"
12366
12632
  },
12367
12633
  dependencies: {
12368
- "@aman_asmuei/aman-core": "^0.3.0",
12369
12634
  "@aman_asmuei/acore-core": "^0.2.0",
12635
+ "@aman_asmuei/aman-core": "^0.3.0",
12636
+ "@aman_asmuei/amem-core": "^0.6.0",
12370
12637
  "@aman_asmuei/arules-core": "^0.2.0",
12371
- "@aman_asmuei/amem-core": "^0.5.0",
12372
12638
  "@anthropic-ai/sdk": "^0.39.0",
12373
12639
  "@clack/prompts": "^0.9.1",
12374
12640
  "@modelcontextprotocol/sdk": "^1.27.1",
@@ -12709,7 +12975,7 @@ function bootstrapEcosystem() {
12709
12975
  return true;
12710
12976
  }
12711
12977
  var program = new Command();
12712
- program.name("aman-agent").description("Your AI companion, running locally").version("0.41.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) => {
12978
+ program.name("aman-agent").description("Your AI companion, running locally").version("0.42.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) => {
12713
12979
  p4.intro(pc9.bold("aman agent") + pc9.dim(" \u2014 your AI companion"));
12714
12980
  let config = loadConfig();
12715
12981
  if (!config) {
@@ -12983,6 +13249,22 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
12983
13249
  } catch (err) {
12984
13250
  p4.log.warning(`Memory initialization failed: ${err instanceof Error ? err.message : String(err)}`);
12985
13251
  }
13252
+ if (isMemoryInitialized()) {
13253
+ const mirrorSpinner = p4.spinner();
13254
+ mirrorSpinner.start("Checking mirror dir for updates");
13255
+ try {
13256
+ const syncResult = await startupAutoSync();
13257
+ mirrorSpinner.stop(
13258
+ syncResult && syncResult.imported > 0 ? `[mirror] synced ${syncResult.imported} new` : ""
13259
+ );
13260
+ } catch (err) {
13261
+ mirrorSpinner.stop(
13262
+ pc9.dim(
13263
+ `[mirror] auto-sync skipped: ${err instanceof Error ? err.message : String(err)}`
13264
+ )
13265
+ );
13266
+ }
13267
+ }
12986
13268
  if (isMemoryInitialized()) {
12987
13269
  const memSpinner = p4.spinner();
12988
13270
  memSpinner.start("Consolidating memory");