myagentmemory 0.5.2 → 0.5.3

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/cli.js CHANGED
@@ -26,13 +26,13 @@ import * as fs from "node:fs";
26
26
  import * as os from "node:os";
27
27
  import * as path from "node:path";
28
28
  import { COMMAND_DESCRIPTIONS, COMMAND_OPTIONS, COMMANDS, GLOBAL_OPTIONS, optionTakesValue, PLUGIN_COMMAND_OPTIONS, renderCommandHelp, SCRATCHPAD_ACTION_OPTIONS, } from "./cli-spec.js";
29
- import { detectCompletionShell, generateCompletion, installCompletion } from "./completions.js";
29
+ import { detectCompletionShell, generateCompletion, installCompletion, uninstallCompletion, } from "./completions.js";
30
30
  import { _setBaseDir, buildDynamicContext, buildMemoryContext, buildStableContext, checkCollection, dailyPath, detectQmd, distilMemories, ensureDirs, ensureQmdAvailableForSync, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdEmbedMode, getQmdHealth, getQmdResultPath, getQmdResultText, getScratchpadFile, getTopicsDir, installSkills, memoryWrite, nowTimestamp, parseScratchpad, probeEmbeddings, readFileSafe, readHookMode, redactSecrets, runQmdEmbedDetached, runQmdSearch, runQmdSync, runQmdUpdateNow, scheduleQmdUpdate, scratchpadAction, searchRelevantMemories, serializeScratchpad, setupQmdCollection, slugifyTopic, todayStr, topicPath, uninstallSkills, } from "./core.js";
31
- import { detectHookAgents, installHooks, isHookInstalled, isStopHookInstalled, isUserPromptSubmitInstalled, uninstallHooks, } from "./hooks.js";
31
+ import { detectHookAgents, getPiMemoryState, installHooks, isHookInstalled, isStopHookInstalled, isUserPromptSubmitInstalled, uninstallHooks, } from "./hooks.js";
32
32
  import { StdioMcpServer } from "./mcp-server.js";
33
- import { createDefaultPluginBootstrap, PluginBootstrapFailure, } from "./plugin-bootstrap.js";
33
+ import { createDefaultPluginBootstrap, getDefaultPluginInstallRoot, PluginBootstrapFailure, } from "./plugin-bootstrap.js";
34
34
  import { InstalledPluginRuntimeV1 } from "./plugin-runtime.js";
35
- import { checkForUpgrades, detectInstallMethod, formatUpgradeNotice, isCacheFresh, readUpgradeCache, refreshUpgradeCacheBackground, runInstaller, } from "./upgrade.js";
35
+ import { checkForUpgrades, detectInstallMethod, formatUpgradeNotice, isCacheFresh, readUpgradeCache, readUpgradePolicy, refreshUpgradeCacheBackground, runInstaller, writeUpgradeCache, writeUpgradePolicy, } from "./upgrade.js";
36
36
  function readPackageVersion() {
37
37
  try {
38
38
  const packageJson = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
@@ -1070,6 +1070,11 @@ async function cmdInstallHooks(flags) {
1070
1070
  const json = hasFlag(flags, "json");
1071
1071
  const requested = getFlag(flags, "only");
1072
1072
  const requestedKeys = requested ? new Set(requested.split(",").map((value) => value.trim())) : null;
1073
+ // Internal-only signal from cmdSetup: pi's action is a real network package
1074
+ // install (`pi install npm:pi-memory`), not a local config-file edit like
1075
+ // every other agent here — so setup's internally-manufactured `yes: true`
1076
+ // must not cover it. Not a public flag; never documented in cli-spec.
1077
+ const deferPi = hasFlag(flags, "_setup-defer-pi");
1073
1078
  const modeFlag = getFlag(flags, "mode");
1074
1079
  if (modeFlag !== undefined && modeFlag !== "stable" && modeFlag !== "per-turn") {
1075
1080
  exitError(`--mode must be 'stable' or 'per-turn' (got ${modeFlag})`, json);
@@ -1078,11 +1083,19 @@ async function cmdInstallHooks(flags) {
1078
1083
  const { homeDir, targets } = detectHookAgents();
1079
1084
  if (!homeDir)
1080
1085
  exitError("Home directory not found.", json);
1081
- const eligible = targets.filter((target) => target.supported && target.detected && (!requestedKeys || requestedKeys.has(target.key)));
1086
+ const eligible = targets.filter((target) => {
1087
+ if (!(target.supported && target.detected))
1088
+ return false;
1089
+ if (requestedKeys && !requestedKeys.has(target.key))
1090
+ return false;
1091
+ if (target.key === "pi" && deferPi)
1092
+ return false;
1093
+ return true;
1094
+ });
1082
1095
  if (!eligible.length) {
1083
- if (json)
1084
- return output({ ok: true, homeDir, results: [] }, true);
1085
- return output("No eligible agents. Nothing to install.", false);
1096
+ const report = { ok: true, homeDir, results: [] };
1097
+ output(json ? report : "No eligible agents. Nothing to install.", json);
1098
+ return report;
1086
1099
  }
1087
1100
  // Consider an agent "already installed" only when the wiring matches the requested mode.
1088
1101
  // per-turn requires BOTH SessionStart and UserPromptSubmit; stable requires SessionStart AND
@@ -1112,43 +1125,50 @@ async function cmdInstallHooks(flags) {
1112
1125
  console.log(`Automatic context already active for: ${labels}.`);
1113
1126
  }
1114
1127
  if (!pending.length) {
1115
- if (json) {
1116
- return output({
1117
- ok: true,
1118
- homeDir,
1119
- results: alreadyInstalled.map((target) => ({
1120
- key: target.key,
1121
- label: target.label,
1122
- installed: false,
1123
- reason: "already installed",
1124
- mode,
1125
- })),
1126
- }, true);
1127
- }
1128
- return output("Nothing to install.", false);
1128
+ const report = {
1129
+ ok: true,
1130
+ homeDir,
1131
+ results: alreadyInstalled.map((target) => ({
1132
+ key: target.key,
1133
+ label: target.label,
1134
+ installed: false,
1135
+ reason: "already installed",
1136
+ mode,
1137
+ })),
1138
+ };
1139
+ output(json ? report : "Nothing to install.", json);
1140
+ return report;
1129
1141
  }
1130
1142
  const selected = new Set();
1131
1143
  const applyAll = hasFlag(flags, "yes") || hasFlag(flags, "all") || !process.stdin.isTTY;
1132
1144
  const hookLabel = mode === "per-turn" ? "SessionStart + UserPromptSubmit hooks" : "SessionStart hook";
1133
1145
  for (const target of pending) {
1134
- if (applyAll || (await promptYesNo(`Install ${hookLabel} for ${target.label}?`, true)))
1146
+ // pi gets a real package install (`pi install npm:pi-memory`), not a config-file hook edit —
1147
+ // word the prompt accordingly so the confirmation matches what actually happens.
1148
+ const question = target.key === "pi"
1149
+ ? `Install pi-memory (native pi extension) for ${target.label}?`
1150
+ : `Install ${hookLabel} for ${target.label}?`;
1151
+ if (applyAll || (await promptYesNo(question, true)))
1135
1152
  selected.add(target.key);
1136
1153
  }
1137
1154
  if (!selected.size) {
1138
- if (json)
1139
- return output({ ok: true, homeDir, results: [] }, true);
1140
- return output("Nothing selected. Skipped.", false);
1155
+ const report = { ok: true, homeDir, results: [] };
1156
+ output(json ? report : "Nothing selected. Skipped.", json);
1157
+ return report;
1141
1158
  }
1142
1159
  const report = installHooks(selected, mode);
1143
1160
  if (!report.ok)
1144
1161
  exitError(report.error ?? "install failed", json);
1145
- if (json)
1146
- return output(report, true);
1162
+ if (json) {
1163
+ output(report, true);
1164
+ return report;
1165
+ }
1147
1166
  for (const result of report.results) {
1148
1167
  console.log(result.installed
1149
1168
  ? `Installed ${result.label} hook (${result.mode ?? mode}): ${result.path}`
1150
1169
  : `Skipped ${result.label} (${result.reason ?? "unknown"})`);
1151
1170
  }
1171
+ return report;
1152
1172
  }
1153
1173
  function cmdUninstallHooks(flags) {
1154
1174
  const json = hasFlag(flags, "json");
@@ -1434,15 +1454,14 @@ async function cmdSetup(flags) {
1434
1454
  const steps = [];
1435
1455
  const runQuiet = async (fn) => {
1436
1456
  if (!json) {
1437
- await fn();
1438
- return;
1457
+ return await fn();
1439
1458
  }
1440
1459
  const originalLog = console.log;
1441
1460
  const originalInfo = console.info;
1442
1461
  console.log = () => { };
1443
1462
  console.info = () => { };
1444
1463
  try {
1445
- await fn();
1464
+ return await fn();
1446
1465
  }
1447
1466
  finally {
1448
1467
  console.log = originalLog;
@@ -1473,8 +1492,31 @@ async function cmdSetup(flags) {
1473
1492
  // Step 3: hooks (uses the improved preflight — silent when all already installed)
1474
1493
  if (!skipHooks) {
1475
1494
  try {
1476
- await runQuiet(() => cmdInstallHooks(subFlags));
1477
- steps.push({ name: "hooks", ok: true });
1495
+ const userSuppliedYes = hasFlag(flags, "yes");
1496
+ // pi's install is a real network package fetch (`pi install npm:pi-memory`),
1497
+ // unlike every other agent here (local config-file edits) — setup's
1498
+ // manufactured `yes: true` above must not silently cover that unless the
1499
+ // user actually asked for --yes themselves.
1500
+ const { homeDir: detectedHomeDir, targets: detectedTargets } = detectHookAgents();
1501
+ const piTarget = detectedTargets.find((target) => target.key === "pi");
1502
+ const piDetected = Boolean(piTarget?.supported && piTarget?.detected);
1503
+ const piAlreadyActive = piDetected && detectedHomeDir ? isHookInstalled(detectedHomeDir, "pi") : false;
1504
+ const hooksFlags = userSuppliedYes ? subFlags : { ...subFlags, "_setup-defer-pi": true };
1505
+ const report = await runQuiet(() => cmdInstallHooks(hooksFlags));
1506
+ const piFailure = report?.results.find((result) => result.key === "pi" && !result.installed && result.reason && result.reason !== "already installed");
1507
+ if (piFailure) {
1508
+ steps.push({ name: "hooks", ok: false, detail: `pi-memory install failed: ${piFailure.reason}` });
1509
+ }
1510
+ else if (piDetected && !piAlreadyActive && !userSuppliedYes) {
1511
+ steps.push({
1512
+ name: "hooks",
1513
+ ok: true,
1514
+ detail: "pi-memory deferred — re-run with --yes, or agent-memory install-hooks --only pi",
1515
+ });
1516
+ }
1517
+ else {
1518
+ steps.push({ name: "hooks", ok: true });
1519
+ }
1478
1520
  }
1479
1521
  catch (error) {
1480
1522
  steps.push({ name: "hooks", ok: false, detail: error.message });
@@ -1665,6 +1707,138 @@ async function cmdSetup(flags) {
1665
1707
  }
1666
1708
  console.log("");
1667
1709
  }
1710
+ /**
1711
+ * Reverse of {@link cmdSetup}: removes every install artifact agent-memory
1712
+ * creates outside of this package — hooks, skills, MCP registrations, shell
1713
+ * completions, and the Pro plugin executables. Memory data under
1714
+ * `getMemoryDir()` (MEMORY.md, daily logs, scratchpad, topics, qmd index) is
1715
+ * left untouched unless `--data` is passed, since that's the one step a user
1716
+ * can't undo. Destructive by nature, so it always requires either an
1717
+ * interactive confirmation or `--yes`.
1718
+ */
1719
+ async function cmdUninstall(flags) {
1720
+ const json = hasFlag(flags, "json");
1721
+ const yes = hasFlag(flags, "yes");
1722
+ const wipeData = hasFlag(flags, "data");
1723
+ const interactive = !json && Boolean(process.stdin.isTTY && process.stdout.isTTY);
1724
+ if (!yes) {
1725
+ const message = wipeData
1726
+ ? "Re-run with --yes to remove agent-memory's hooks, skills, MCP registrations, completions, and Pro plugin, and permanently delete ~/.agent-memory (MEMORY.md, daily logs, scratchpad, topics, qmd index)."
1727
+ : "Re-run with --yes to remove agent-memory's hooks, skills, MCP registrations, completions, and Pro plugin. Your memory data is left untouched.";
1728
+ if (interactive) {
1729
+ const question = wipeData
1730
+ ? "This will also permanently delete your memory data (MEMORY.md, daily logs, scratchpad). Continue?"
1731
+ : "Remove agent-memory's hooks, skills, MCP registrations, completions, and Pro plugin?";
1732
+ if (!(await promptYesNo(question, false))) {
1733
+ console.log("Aborted. Nothing was removed.");
1734
+ return;
1735
+ }
1736
+ }
1737
+ else {
1738
+ if (json)
1739
+ output({ ok: false, error: { code: "confirmation_required", message } }, true);
1740
+ else
1741
+ console.error(`Error: ${message}`);
1742
+ process.exitCode = 1;
1743
+ return;
1744
+ }
1745
+ }
1746
+ const steps = [];
1747
+ try {
1748
+ const report = uninstallSkills();
1749
+ if (!report.ok)
1750
+ throw new Error(report.error ?? "failed to remove skills");
1751
+ steps.push({
1752
+ name: "skills",
1753
+ ok: true,
1754
+ detail: report.removed.length ? `removed ${report.removed.length}` : "not installed",
1755
+ });
1756
+ }
1757
+ catch (error) {
1758
+ steps.push({ name: "skills", ok: false, detail: error.message });
1759
+ }
1760
+ try {
1761
+ const report = uninstallHooks();
1762
+ if (!report.ok)
1763
+ throw new Error(report.error ?? "failed to remove hooks");
1764
+ const removed = report.results.filter((r) => r.installed).length;
1765
+ // agent-memory never removes pi-memory itself (see uninstallPiMemoryDelegate) — make that
1766
+ // explicit here rather than letting a generic "removed N" detail imply everything is gone.
1767
+ const piResult = report.results.find((r) => r.key === "pi");
1768
+ const piNote = piResult?.reason?.startsWith("pi-memory left installed") ? `; ${piResult.reason}` : "";
1769
+ steps.push({
1770
+ name: "hooks",
1771
+ ok: true,
1772
+ detail: (removed ? `removed ${removed}` : "not installed") + piNote,
1773
+ });
1774
+ }
1775
+ catch (error) {
1776
+ steps.push({ name: "hooks", ok: false, detail: error.message });
1777
+ }
1778
+ try {
1779
+ const results = unregisterMcpFromAgents(null);
1780
+ const removed = results.filter((r) => r.status === "unregistered").length;
1781
+ steps.push({ name: "mcp", ok: true, detail: removed ? `unregistered ${removed}` : "not registered" });
1782
+ }
1783
+ catch (error) {
1784
+ steps.push({ name: "mcp", ok: false, detail: error.message });
1785
+ }
1786
+ try {
1787
+ const results = uninstallCompletion();
1788
+ const removed = results.filter((r) => r.removed || r.profileUpdated).length;
1789
+ steps.push({ name: "completions", ok: true, detail: removed ? `removed ${removed}` : "not installed" });
1790
+ }
1791
+ catch (error) {
1792
+ steps.push({ name: "completions", ok: false, detail: error.message });
1793
+ }
1794
+ try {
1795
+ const manager = createDefaultPluginBootstrap(VERSION);
1796
+ const pluginResult = await manager.uninstall();
1797
+ steps.push({ name: "plugin", ok: pluginResult.ok, detail: pluginResult.result });
1798
+ }
1799
+ catch (error) {
1800
+ steps.push({ name: "plugin", ok: false, detail: error.message });
1801
+ }
1802
+ if (wipeData) {
1803
+ try {
1804
+ const memoryDir = getMemoryDir();
1805
+ if (fs.existsSync(memoryDir))
1806
+ fs.rmSync(memoryDir, { recursive: true, force: true });
1807
+ const pluginRoot = getDefaultPluginInstallRoot();
1808
+ if (fs.existsSync(pluginRoot))
1809
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
1810
+ steps.push({ name: "data", ok: true, detail: memoryDir });
1811
+ }
1812
+ catch (error) {
1813
+ steps.push({ name: "data", ok: false, detail: error.message });
1814
+ }
1815
+ }
1816
+ const allOk = steps.every((step) => step.ok);
1817
+ if (!allOk)
1818
+ process.exitCode = 1;
1819
+ if (json) {
1820
+ output({ ok: allOk, data: wipeData, steps }, true);
1821
+ return;
1822
+ }
1823
+ console.log("");
1824
+ console.log(colorize("agent-memory uninstall", "bold"));
1825
+ for (const step of steps) {
1826
+ const mark = step.ok ? MARK_OK : MARK_FAIL;
1827
+ const detail = step.detail ? colorize(` ${step.detail}`, "dim") : "";
1828
+ console.log(` ${mark} ${step.name}${detail}`);
1829
+ }
1830
+ console.log("");
1831
+ if (!allOk) {
1832
+ console.log(colorize("Some steps failed — see details above.", "yellow"));
1833
+ }
1834
+ else if (wipeData) {
1835
+ console.log(colorize("agent-memory has been fully removed, including your memory data.", "green"));
1836
+ }
1837
+ else {
1838
+ console.log(colorize("agent-memory's install artifacts have been removed.", "green"));
1839
+ console.log(colorize(`Your notes are untouched at ${getMemoryDir()}. Re-run with --data to remove them too.`, "dim"));
1840
+ }
1841
+ }
1668
1842
  /**
1669
1843
  * Explain the optional plugin without making a successful core setup feel
1670
1844
  * incomplete. Keep the quota line here in sync with `freeEntitlement()`.
@@ -1900,6 +2074,14 @@ async function cmdDoctor(flags) {
1900
2074
  });
1901
2075
  }
1902
2076
  }
2077
+ // Auto-upgrade policy
2078
+ const upgradePolicy = readUpgradePolicy();
2079
+ rows.push({
2080
+ status: "ok",
2081
+ label: "Auto-upgrade",
2082
+ detail: `CLI: ${upgradePolicy.cli}, Pro: ${upgradePolicy.plugin}`,
2083
+ fix: upgradePolicy.cli === "off" && upgradePolicy.plugin === "off" ? "agent-memory upgrade policy auto" : undefined,
2084
+ });
1903
2085
  // Skills + hooks per detected host
1904
2086
  const { homeDir, targets } = detectHookAgents();
1905
2087
  const detected = targets.filter((target) => target.detected);
@@ -1915,7 +2097,7 @@ async function cmdDoctor(flags) {
1915
2097
  rows.push({
1916
2098
  status: "warn",
1917
2099
  label: "Agent hosts",
1918
- detail: "no supported agents detected (Claude Code, Codex, Cursor, opencode)",
2100
+ detail: "no supported agents detected (Claude Code, Codex, Cursor, opencode, pi)",
1919
2101
  fix: "install one of the agents first, then: agent-memory install-skills",
1920
2102
  });
1921
2103
  }
@@ -1927,7 +2109,35 @@ async function cmdDoctor(flags) {
1927
2109
  });
1928
2110
  for (const target of detected) {
1929
2111
  if (!target.supported) {
1930
- // Skip skill/hook rows for hosts we don't yet integrate with (e.g. pi has its own extension).
2112
+ // Skip skill/hook rows for hosts we don't yet integrate with.
2113
+ continue;
2114
+ }
2115
+ if (target.key === "pi") {
2116
+ // pi has no SKILL.md — agent-memory delegates entirely to the pi-memory
2117
+ // extension, installed via `pi install npm:pi-memory` rather than a
2118
+ // config-file edit, so it gets its own row instead of the generic
2119
+ // Skill:/Hook: pair below.
2120
+ const installed = homeDir ? isHookInstalled(homeDir, "pi") : false;
2121
+ const state = getPiMemoryState();
2122
+ let detail;
2123
+ if (installed) {
2124
+ detail = "pi-memory extension active (github.com/jayzeng/pi-memory)";
2125
+ }
2126
+ else if (state && !state.ok) {
2127
+ detail = `pi-memory not detected — last install attempt (${state.lastAttemptAt}) failed: ${state.detail}`;
2128
+ }
2129
+ else if (state?.ok) {
2130
+ detail = `pi-memory installed (${state.lastAttemptAt}) but no session has run yet`;
2131
+ }
2132
+ else {
2133
+ detail = "pi-memory not detected — agent-memory can install it";
2134
+ }
2135
+ rows.push({
2136
+ status: installed ? "ok" : "warn",
2137
+ label: "Memory: pi",
2138
+ detail,
2139
+ fix: installed ? undefined : "agent-memory install-hooks --only pi",
2140
+ });
1931
2141
  continue;
1932
2142
  }
1933
2143
  const skillPath = homeDir ? `${target.homeMarker}/skills/agent-memory/SKILL.md` : null;
@@ -2482,16 +2692,108 @@ async function resolvePluginLatestHint() {
2482
2692
  return { latest: null, updateAvailable: false };
2483
2693
  }
2484
2694
  }
2485
- async function cmdUpgrade(flags) {
2695
+ async function cmdUpgradePolicy(flags, positional) {
2696
+ const json = hasFlag(flags, "json");
2697
+ const value = positional[0];
2698
+ if (!value) {
2699
+ const policy = readUpgradePolicy();
2700
+ if (json)
2701
+ output({ cli: policy.cli, plugin: policy.plugin }, true);
2702
+ else
2703
+ console.log(` cli: ${policy.cli}\n plugin: ${policy.plugin}`);
2704
+ return;
2705
+ }
2706
+ if (value !== "off" && value !== "notify" && value !== "auto") {
2707
+ exitError(`Invalid policy '${value}'. Expected one of: off, notify, auto.`, json);
2708
+ }
2709
+ const onlyCli = hasFlag(flags, "cli");
2710
+ const onlyPlugin = hasFlag(flags, "plugin");
2711
+ const patch = {};
2712
+ if (onlyCli || !onlyPlugin)
2713
+ patch.cli = value;
2714
+ if (onlyPlugin || !onlyCli)
2715
+ patch.plugin = value;
2716
+ const next = writeUpgradePolicy(patch);
2717
+ if (json)
2718
+ output(next, true);
2719
+ else
2720
+ console.log(` cli: ${next.cli}\n plugin: ${next.plugin}`);
2721
+ }
2722
+ /**
2723
+ * `--background`-mode install: only touches a target when its persisted policy
2724
+ * (see `readUpgradePolicy`) is `"auto"`. Always non-interactive — this path is
2725
+ * only ever reached from a detached, non-TTY child spawned by
2726
+ * `refreshUpgradeCacheBackground()`. Failures are recorded, never thrown.
2727
+ */
2728
+ async function runAutoUpgrade(status, policy, pluginCurrent, quiet) {
2729
+ const now = new Date().toISOString();
2730
+ let cliAuto;
2731
+ let pluginAuto;
2732
+ if (policy.cli === "auto" && status.cli.upgradeAvailable) {
2733
+ const method = detectInstallMethod();
2734
+ if (!quiet)
2735
+ console.log(`Auto-upgrading CLI via: ${method.command.join(" ")}`);
2736
+ try {
2737
+ const result = runInstaller(method);
2738
+ cliAuto = result.ok
2739
+ ? { at: now, ok: true, version: status.cli.latest }
2740
+ : {
2741
+ at: now,
2742
+ ok: false,
2743
+ version: status.cli.current,
2744
+ error: (result.stderr || result.stdout || `exit ${result.code}`).trim(),
2745
+ };
2746
+ }
2747
+ catch (error) {
2748
+ cliAuto = {
2749
+ at: now,
2750
+ ok: false,
2751
+ version: status.cli.current,
2752
+ error: error instanceof Error ? error.message : String(error),
2753
+ };
2754
+ }
2755
+ }
2756
+ if (policy.plugin === "auto" && status.plugin.upgradeAvailable) {
2757
+ if (!quiet)
2758
+ console.log("Auto-upgrading Pro plugin bundle…");
2759
+ try {
2760
+ const pluginResult = await cmdPlugin({ json: true }, ["update"]);
2761
+ const ok = Boolean(pluginResult?.ok);
2762
+ pluginAuto = ok
2763
+ ? { at: now, ok: true, version: status.plugin.latest }
2764
+ : {
2765
+ at: now,
2766
+ ok: false,
2767
+ version: pluginCurrent,
2768
+ error: pluginResult?.error?.message ?? "plugin update failed",
2769
+ };
2770
+ }
2771
+ catch (error) {
2772
+ pluginAuto = {
2773
+ at: now,
2774
+ ok: false,
2775
+ version: pluginCurrent,
2776
+ error: error instanceof Error ? error.message : String(error),
2777
+ };
2778
+ }
2779
+ }
2780
+ return { cliAuto, pluginAuto };
2781
+ }
2782
+ async function cmdUpgrade(flags, positional = []) {
2783
+ if (positional[0] === "policy") {
2784
+ await cmdUpgradePolicy(flags, positional.slice(1));
2785
+ return;
2786
+ }
2486
2787
  const json = hasFlag(flags, "json");
2487
2788
  const quiet = hasFlag(flags, "quiet");
2488
2789
  const checkOnly = hasFlag(flags, "check");
2790
+ const background = hasFlag(flags, "background");
2489
2791
  const refresh = hasFlag(flags, "refresh");
2490
2792
  const onlyCli = hasFlag(flags, "cli");
2491
2793
  const onlyPlugin = hasFlag(flags, "plugin");
2492
2794
  const targetCli = onlyCli || !onlyPlugin;
2493
2795
  const targetPlugin = onlyPlugin || !onlyCli;
2494
- const applyAll = hasFlag(flags, "yes") || !process.stdin.isTTY || !process.stdout.isTTY;
2796
+ const applyAll = hasFlag(flags, "yes") || background || !process.stdin.isTTY || !process.stdout.isTTY;
2495
2797
  const pluginCurrent = await readPluginCurrentVersion();
2496
2798
  const pluginProbe = targetPlugin ? await resolvePluginLatestHint() : { latest: null, updateAvailable: false };
2497
2799
  const status = await checkForUpgrades({
@@ -2508,6 +2810,30 @@ async function cmdUpgrade(flags) {
2508
2810
  printUpgradeStatus(status, { targetCli, targetPlugin });
2509
2811
  return;
2510
2812
  }
2813
+ if (background) {
2814
+ const policy = readUpgradePolicy();
2815
+ const { cliAuto, pluginAuto } = await runAutoUpgrade(status, policy, pluginCurrent, quiet);
2816
+ writeUpgradeCache({
2817
+ checkedAt: status.checkedAt,
2818
+ cliCurrent: VERSION,
2819
+ cliLatest: status.cli.latest,
2820
+ pluginCurrent,
2821
+ pluginLatest: status.plugin.latest,
2822
+ cliAuto,
2823
+ pluginAuto,
2824
+ });
2825
+ if (json)
2826
+ output({ ...status, cliAuto, pluginAuto }, true);
2827
+ else if (!quiet) {
2828
+ if (cliAuto)
2829
+ console.log(` ${cliAuto.ok ? MARK_OK : MARK_FAIL} CLI auto-upgrade: ${cliAuto.ok ? `→ ${cliAuto.version}` : cliAuto.error}`);
2830
+ if (pluginAuto)
2831
+ console.log(` ${pluginAuto.ok ? MARK_OK : MARK_FAIL} Pro auto-upgrade: ${pluginAuto.ok ? `→ ${pluginAuto.version}` : pluginAuto.error}`);
2832
+ }
2833
+ if ((cliAuto && !cliAuto.ok) || (pluginAuto && !pluginAuto.ok))
2834
+ process.exitCode = 1;
2835
+ return;
2836
+ }
2511
2837
  const cliNeedsUpgrade = targetCli && status.cli.upgradeAvailable;
2512
2838
  const pluginNeedsUpgrade = targetPlugin && status.plugin.upgradeAvailable;
2513
2839
  if (!cliNeedsUpgrade && !pluginNeedsUpgrade) {
@@ -2648,6 +2974,64 @@ function registerMcpInAgents(only) {
2648
2974
  }
2649
2975
  return results;
2650
2976
  }
2977
+ /**
2978
+ * Reverse of {@link registerMcpInAgents}: removes the `agent-memory` MCP server
2979
+ * entry from every supported local harness. Missing config files are reported
2980
+ * as `not-installed`, files that never had the entry as `not-registered`.
2981
+ */
2982
+ function unregisterMcpFromAgents(only) {
2983
+ const home = os.homedir();
2984
+ const want = (key) => !only || only.has(key);
2985
+ const results = [];
2986
+ const unregisterJson = (key, displayName, configFile) => {
2987
+ const p = path.join(home, configFile);
2988
+ if (!fs.existsSync(p)) {
2989
+ results.push({ key, displayName, path: p, status: "not-installed" });
2990
+ return;
2991
+ }
2992
+ let s = {};
2993
+ try {
2994
+ s = JSON.parse(fs.readFileSync(p, "utf8"));
2995
+ }
2996
+ catch { }
2997
+ const servers = (s.mcpServers ?? {});
2998
+ if (!servers["agent-memory"]) {
2999
+ results.push({ key, displayName, path: p, status: "not-registered" });
3000
+ return;
3001
+ }
3002
+ delete servers["agent-memory"];
3003
+ if (Object.keys(servers).length === 0)
3004
+ delete s.mcpServers;
3005
+ else
3006
+ s.mcpServers = servers;
3007
+ fs.writeFileSync(p, `${JSON.stringify(s, null, 2)}\n`);
3008
+ results.push({ key, displayName, path: p, status: "unregistered" });
3009
+ };
3010
+ if (want("claude"))
3011
+ unregisterJson("claude", "Claude Code", ".claude.json");
3012
+ if (want("cursor"))
3013
+ unregisterJson("cursor", "Cursor", ".cursor/mcp.json");
3014
+ if (want("windsurf"))
3015
+ unregisterJson("windsurf", "Windsurf", ".windsurf/mcp_settings.json");
3016
+ if (want("codex")) {
3017
+ const p = path.join(home, ".codex", "config.toml");
3018
+ if (!fs.existsSync(p)) {
3019
+ results.push({ key: "codex", displayName: "Codex", path: p, status: "not-installed" });
3020
+ }
3021
+ else {
3022
+ const existing = fs.readFileSync(p, "utf8");
3023
+ if (!existing.includes("[mcp_servers.agent-memory]")) {
3024
+ results.push({ key: "codex", displayName: "Codex", path: p, status: "not-registered" });
3025
+ }
3026
+ else {
3027
+ const pattern = /\n?\[mcp_servers\.agent-memory\]\n(?:(?!\[)[^\n]*\n?)*/;
3028
+ fs.writeFileSync(p, existing.replace(pattern, ""), "utf8");
3029
+ results.push({ key: "codex", displayName: "Codex", path: p, status: "unregistered" });
3030
+ }
3031
+ }
3032
+ }
3033
+ return results;
3034
+ }
2651
3035
  async function cmdServe(flags) {
2652
3036
  const isMcp = hasFlag(flags, "mcp");
2653
3037
  const isRegister = hasFlag(flags, "register");
@@ -2689,7 +3073,7 @@ async function cmdServe(flags) {
2689
3073
  // Core tools: free tier, available without Pro.
2690
3074
  server.addTool({
2691
3075
  name: "memory_read",
2692
- description: "Read the current long-term memory (MEMORY.md) and scratchpad checklist.",
3076
+ description: "Read ONLY the curated long-term memory (MEMORY.md) and open scratchpad items — a small saved snapshot, not a search. For finding things in daily logs/topics, or recalling past chat sessions, use the `agent-memory` skill (search/recall commands) instead of this tool.",
2693
3077
  inputSchema: { type: "object", properties: {} },
2694
3078
  }, async () => {
2695
3079
  const memFile = getMemoryFile();
@@ -2753,7 +3137,7 @@ function printUsage() {
2753
3137
  ["Do things", ["save", "note", "recall", "search"]],
2754
3138
  ["See things", ["status", "doctor", "dashboard"]],
2755
3139
  ["Advanced", ["write", "read", "context", "scratchpad", "distil", "sync"]],
2756
- ["Setup", ["setup", "install-skills", "install-hooks", "completion"]],
3140
+ ["Setup", ["setup", "install-skills", "install-hooks", "completion", "uninstall"]],
2757
3141
  ["Pro", ["pro", "learn"]],
2758
3142
  ];
2759
3143
  const knownGroupCommands = new Set(groups.flatMap(([, list]) => list));
@@ -2854,7 +3238,7 @@ async function main() {
2854
3238
  process.stdout.isTTY &&
2855
3239
  !json &&
2856
3240
  command &&
2857
- !["init", "help", "version", "doctor", "status", "completion", "hook", "serve"].includes(command) &&
3241
+ !["init", "help", "version", "doctor", "status", "completion", "hook", "serve", "uninstall"].includes(command) &&
2858
3242
  !fs.existsSync(getMemoryDir())) {
2859
3243
  console.log(colorize("It looks like this is your first run — no memory directory yet.", "yellow"));
2860
3244
  if (await promptYesNo("Run agent-memory init to set things up?", true)) {
@@ -2966,6 +3350,9 @@ async function main() {
2966
3350
  case "uninstall-hooks":
2967
3351
  cmdUninstallHooks(flags);
2968
3352
  break;
3353
+ case "uninstall":
3354
+ await cmdUninstall(flags);
3355
+ break;
2969
3356
  case "hook": {
2970
3357
  const sub = positional[0];
2971
3358
  if (sub !== "session-start" && sub !== "user-prompt-submit" && sub !== "stop") {
@@ -2988,6 +3375,13 @@ async function main() {
2988
3375
  const layer = readHookMode() === "per-turn" ? "stable" : undefined;
2989
3376
  await cmdContext(layer ? { "no-search": true, layer } : { "no-search": true });
2990
3377
  try {
3378
+ const policy = readUpgradePolicy();
3379
+ if (!policy.existed) {
3380
+ // Persist the (possibly env-overridden) defaults now, so this notice
3381
+ // only ever fires once — the file's mere existence is the "seen" flag.
3382
+ writeUpgradePolicy({ cli: policy.cli, plugin: policy.plugin });
3383
+ console.error("agent-memory: auto-upgrade is on by default (CLI + Pro plugin). Disable with: agent-memory upgrade policy off");
3384
+ }
2991
3385
  const cache = readUpgradeCache();
2992
3386
  if (cache) {
2993
3387
  const status = await checkForUpgrades({
@@ -2995,7 +3389,7 @@ async function main() {
2995
3389
  pluginCurrent: cache.pluginCurrent,
2996
3390
  cacheOnly: true,
2997
3391
  });
2998
- const notice = formatUpgradeNotice(status);
3392
+ const notice = formatUpgradeNotice(status, cache);
2999
3393
  if (notice)
3000
3394
  console.error(notice);
3001
3395
  }
@@ -3041,7 +3435,7 @@ async function main() {
3041
3435
  await cmdPro(flags, positional);
3042
3436
  break;
3043
3437
  case "upgrade":
3044
- await cmdUpgrade(flags);
3438
+ await cmdUpgrade(flags, positional);
3045
3439
  break;
3046
3440
  case "serve":
3047
3441
  await cmdServe(flags);
@@ -5,9 +5,21 @@ export interface CompletionInstallResult {
5
5
  profilePath?: string;
6
6
  profileUpdated: boolean;
7
7
  }
8
+ export interface CompletionUninstallResult {
9
+ shell: CompletionShell;
10
+ completionPath: string;
11
+ removed: boolean;
12
+ profilePath?: string;
13
+ profileUpdated: boolean;
14
+ }
8
15
  export declare function generateCompletion(shell: CompletionShell): string;
9
16
  export declare function detectCompletionShell(environment?: Record<string, string | undefined>, platform?: NodeJS.Platform): CompletionShell | null;
10
17
  export declare function installCompletion(shell: CompletionShell, options?: {
11
18
  homeDir?: string;
12
19
  platform?: NodeJS.Platform;
13
20
  }): CompletionInstallResult;
21
+ /** Reverse of {@link installCompletion} across every supported shell. */
22
+ export declare function uninstallCompletion(options?: {
23
+ homeDir?: string;
24
+ platform?: NodeJS.Platform;
25
+ }): CompletionUninstallResult[];