myagentmemory 0.5.1 → 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/README.md +5 -2
- package/dist/cli-spec.d.ts +1 -1
- package/dist/cli-spec.js +15 -3
- package/dist/cli.js +444 -46
- package/dist/completions.d.ts +12 -0
- package/dist/completions.js +51 -0
- package/dist/core.js +11 -0
- package/dist/hooks.d.ts +15 -1
- package/dist/hooks.js +190 -5
- package/dist/plugin-host.d.ts +1 -0
- package/dist/plugin-runtime.d.ts +9 -0
- package/dist/plugin-runtime.js +21 -0
- package/dist/plugin-service.js +0 -6
- package/dist/upgrade.d.ts +51 -6
- package/dist/upgrade.js +110 -10
- package/docs/official-plugin-bootstrap.md +1 -1
- package/package.json +1 -1
- package/scripts/install-skills.sh +4 -1
- package/skills/agent/SKILL.md +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/codex/SKILL.md +1 -1
- package/skills/cursor/SKILL.md +1 -1
- package/skills/qoder/SKILL.md +164 -0
- package/src/cli-spec.ts +18 -3
- package/src/completions.ts +73 -0
- package/src/core.ts +11 -0
- package/src/hooks.ts +199 -6
- package/src/plugin-host.ts +1 -0
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"));
|
|
@@ -617,8 +617,11 @@ async function cmdUserPromptSubmit(_flags) {
|
|
|
617
617
|
}
|
|
618
618
|
// How many Stop events must elapse (per session_id) before the periodic
|
|
619
619
|
// memory-write nudge fires again. Balances "long sessions get checked
|
|
620
|
-
// repeatedly" against "don't block every single turn".
|
|
621
|
-
|
|
620
|
+
// repeatedly" against "don't block every single turn". Deliberately short —
|
|
621
|
+
// most real sessions are well under a dozen turns, so a wider interval meant
|
|
622
|
+
// the nudge rarely fired in practice (see stop-hook.json in the wild: sessions
|
|
623
|
+
// topping out around 7 turns, zero nags ever recorded).
|
|
624
|
+
const STOP_NAG_INTERVAL = 6;
|
|
622
625
|
// Bound state/stop-hook.json so it can't grow unboundedly across many sessions.
|
|
623
626
|
const STOP_HOOK_MAX_SESSIONS = 50;
|
|
624
627
|
function stopHookStatePath() {
|
|
@@ -1067,6 +1070,11 @@ async function cmdInstallHooks(flags) {
|
|
|
1067
1070
|
const json = hasFlag(flags, "json");
|
|
1068
1071
|
const requested = getFlag(flags, "only");
|
|
1069
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");
|
|
1070
1078
|
const modeFlag = getFlag(flags, "mode");
|
|
1071
1079
|
if (modeFlag !== undefined && modeFlag !== "stable" && modeFlag !== "per-turn") {
|
|
1072
1080
|
exitError(`--mode must be 'stable' or 'per-turn' (got ${modeFlag})`, json);
|
|
@@ -1075,11 +1083,19 @@ async function cmdInstallHooks(flags) {
|
|
|
1075
1083
|
const { homeDir, targets } = detectHookAgents();
|
|
1076
1084
|
if (!homeDir)
|
|
1077
1085
|
exitError("Home directory not found.", json);
|
|
1078
|
-
const eligible = targets.filter((target) =>
|
|
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
|
+
});
|
|
1079
1095
|
if (!eligible.length) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
return
|
|
1096
|
+
const report = { ok: true, homeDir, results: [] };
|
|
1097
|
+
output(json ? report : "No eligible agents. Nothing to install.", json);
|
|
1098
|
+
return report;
|
|
1083
1099
|
}
|
|
1084
1100
|
// Consider an agent "already installed" only when the wiring matches the requested mode.
|
|
1085
1101
|
// per-turn requires BOTH SessionStart and UserPromptSubmit; stable requires SessionStart AND
|
|
@@ -1109,43 +1125,50 @@ async function cmdInstallHooks(flags) {
|
|
|
1109
1125
|
console.log(`Automatic context already active for: ${labels}.`);
|
|
1110
1126
|
}
|
|
1111
1127
|
if (!pending.length) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
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;
|
|
1126
1141
|
}
|
|
1127
1142
|
const selected = new Set();
|
|
1128
1143
|
const applyAll = hasFlag(flags, "yes") || hasFlag(flags, "all") || !process.stdin.isTTY;
|
|
1129
1144
|
const hookLabel = mode === "per-turn" ? "SessionStart + UserPromptSubmit hooks" : "SessionStart hook";
|
|
1130
1145
|
for (const target of pending) {
|
|
1131
|
-
|
|
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)))
|
|
1132
1152
|
selected.add(target.key);
|
|
1133
1153
|
}
|
|
1134
1154
|
if (!selected.size) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
return
|
|
1155
|
+
const report = { ok: true, homeDir, results: [] };
|
|
1156
|
+
output(json ? report : "Nothing selected. Skipped.", json);
|
|
1157
|
+
return report;
|
|
1138
1158
|
}
|
|
1139
1159
|
const report = installHooks(selected, mode);
|
|
1140
1160
|
if (!report.ok)
|
|
1141
1161
|
exitError(report.error ?? "install failed", json);
|
|
1142
|
-
if (json)
|
|
1143
|
-
|
|
1162
|
+
if (json) {
|
|
1163
|
+
output(report, true);
|
|
1164
|
+
return report;
|
|
1165
|
+
}
|
|
1144
1166
|
for (const result of report.results) {
|
|
1145
1167
|
console.log(result.installed
|
|
1146
1168
|
? `Installed ${result.label} hook (${result.mode ?? mode}): ${result.path}`
|
|
1147
1169
|
: `Skipped ${result.label} (${result.reason ?? "unknown"})`);
|
|
1148
1170
|
}
|
|
1171
|
+
return report;
|
|
1149
1172
|
}
|
|
1150
1173
|
function cmdUninstallHooks(flags) {
|
|
1151
1174
|
const json = hasFlag(flags, "json");
|
|
@@ -1431,15 +1454,14 @@ async function cmdSetup(flags) {
|
|
|
1431
1454
|
const steps = [];
|
|
1432
1455
|
const runQuiet = async (fn) => {
|
|
1433
1456
|
if (!json) {
|
|
1434
|
-
await fn();
|
|
1435
|
-
return;
|
|
1457
|
+
return await fn();
|
|
1436
1458
|
}
|
|
1437
1459
|
const originalLog = console.log;
|
|
1438
1460
|
const originalInfo = console.info;
|
|
1439
1461
|
console.log = () => { };
|
|
1440
1462
|
console.info = () => { };
|
|
1441
1463
|
try {
|
|
1442
|
-
await fn();
|
|
1464
|
+
return await fn();
|
|
1443
1465
|
}
|
|
1444
1466
|
finally {
|
|
1445
1467
|
console.log = originalLog;
|
|
@@ -1470,8 +1492,31 @@ async function cmdSetup(flags) {
|
|
|
1470
1492
|
// Step 3: hooks (uses the improved preflight — silent when all already installed)
|
|
1471
1493
|
if (!skipHooks) {
|
|
1472
1494
|
try {
|
|
1473
|
-
|
|
1474
|
-
|
|
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
|
+
}
|
|
1475
1520
|
}
|
|
1476
1521
|
catch (error) {
|
|
1477
1522
|
steps.push({ name: "hooks", ok: false, detail: error.message });
|
|
@@ -1662,6 +1707,138 @@ async function cmdSetup(flags) {
|
|
|
1662
1707
|
}
|
|
1663
1708
|
console.log("");
|
|
1664
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
|
+
}
|
|
1665
1842
|
/**
|
|
1666
1843
|
* Explain the optional plugin without making a successful core setup feel
|
|
1667
1844
|
* incomplete. Keep the quota line here in sync with `freeEntitlement()`.
|
|
@@ -1897,6 +2074,14 @@ async function cmdDoctor(flags) {
|
|
|
1897
2074
|
});
|
|
1898
2075
|
}
|
|
1899
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
|
+
});
|
|
1900
2085
|
// Skills + hooks per detected host
|
|
1901
2086
|
const { homeDir, targets } = detectHookAgents();
|
|
1902
2087
|
const detected = targets.filter((target) => target.detected);
|
|
@@ -1912,7 +2097,7 @@ async function cmdDoctor(flags) {
|
|
|
1912
2097
|
rows.push({
|
|
1913
2098
|
status: "warn",
|
|
1914
2099
|
label: "Agent hosts",
|
|
1915
|
-
detail: "no supported agents detected (Claude Code, Codex, Cursor, opencode)",
|
|
2100
|
+
detail: "no supported agents detected (Claude Code, Codex, Cursor, opencode, pi)",
|
|
1916
2101
|
fix: "install one of the agents first, then: agent-memory install-skills",
|
|
1917
2102
|
});
|
|
1918
2103
|
}
|
|
@@ -1924,7 +2109,35 @@ async function cmdDoctor(flags) {
|
|
|
1924
2109
|
});
|
|
1925
2110
|
for (const target of detected) {
|
|
1926
2111
|
if (!target.supported) {
|
|
1927
|
-
// Skip skill/hook rows for hosts we don't yet integrate with
|
|
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
|
+
});
|
|
1928
2141
|
continue;
|
|
1929
2142
|
}
|
|
1930
2143
|
const skillPath = homeDir ? `${target.homeMarker}/skills/agent-memory/SKILL.md` : null;
|
|
@@ -2479,16 +2692,108 @@ async function resolvePluginLatestHint() {
|
|
|
2479
2692
|
return { latest: null, updateAvailable: false };
|
|
2480
2693
|
}
|
|
2481
2694
|
}
|
|
2482
|
-
async function
|
|
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
|
+
}
|
|
2483
2787
|
const json = hasFlag(flags, "json");
|
|
2484
2788
|
const quiet = hasFlag(flags, "quiet");
|
|
2485
2789
|
const checkOnly = hasFlag(flags, "check");
|
|
2790
|
+
const background = hasFlag(flags, "background");
|
|
2486
2791
|
const refresh = hasFlag(flags, "refresh");
|
|
2487
2792
|
const onlyCli = hasFlag(flags, "cli");
|
|
2488
2793
|
const onlyPlugin = hasFlag(flags, "plugin");
|
|
2489
2794
|
const targetCli = onlyCli || !onlyPlugin;
|
|
2490
2795
|
const targetPlugin = onlyPlugin || !onlyCli;
|
|
2491
|
-
const applyAll = hasFlag(flags, "yes") || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
2796
|
+
const applyAll = hasFlag(flags, "yes") || background || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
2492
2797
|
const pluginCurrent = await readPluginCurrentVersion();
|
|
2493
2798
|
const pluginProbe = targetPlugin ? await resolvePluginLatestHint() : { latest: null, updateAvailable: false };
|
|
2494
2799
|
const status = await checkForUpgrades({
|
|
@@ -2505,6 +2810,30 @@ async function cmdUpgrade(flags) {
|
|
|
2505
2810
|
printUpgradeStatus(status, { targetCli, targetPlugin });
|
|
2506
2811
|
return;
|
|
2507
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
|
+
}
|
|
2508
2837
|
const cliNeedsUpgrade = targetCli && status.cli.upgradeAvailable;
|
|
2509
2838
|
const pluginNeedsUpgrade = targetPlugin && status.plugin.upgradeAvailable;
|
|
2510
2839
|
if (!cliNeedsUpgrade && !pluginNeedsUpgrade) {
|
|
@@ -2645,6 +2974,64 @@ function registerMcpInAgents(only) {
|
|
|
2645
2974
|
}
|
|
2646
2975
|
return results;
|
|
2647
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
|
+
}
|
|
2648
3035
|
async function cmdServe(flags) {
|
|
2649
3036
|
const isMcp = hasFlag(flags, "mcp");
|
|
2650
3037
|
const isRegister = hasFlag(flags, "register");
|
|
@@ -2686,12 +3073,13 @@ async function cmdServe(flags) {
|
|
|
2686
3073
|
// Core tools: free tier, available without Pro.
|
|
2687
3074
|
server.addTool({
|
|
2688
3075
|
name: "memory_read",
|
|
2689
|
-
description: "Read the
|
|
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.",
|
|
2690
3077
|
inputSchema: { type: "object", properties: {} },
|
|
2691
3078
|
}, async () => {
|
|
2692
3079
|
const memFile = getMemoryFile();
|
|
2693
3080
|
const scratchFile = getScratchpadFile();
|
|
2694
|
-
const memory = redactSecrets(readFileSafe(memFile) ?? "").content ||
|
|
3081
|
+
const memory = redactSecrets(readFileSafe(memFile) ?? "").content ||
|
|
3082
|
+
'(empty — this only covers what was explicitly saved. For things said in prior chat sessions, try `agent-memory recall "<query>"` or the `session_recall`/`session_search` MCP tools, if AgentMemory Pro is installed.)';
|
|
2695
3083
|
const scratchRaw = readFileSafe(scratchFile) ?? "";
|
|
2696
3084
|
const scratchItems = parseScratchpad(scratchRaw)
|
|
2697
3085
|
.filter((item) => !item.done)
|
|
@@ -2732,7 +3120,7 @@ async function cmdServe(flags) {
|
|
|
2732
3120
|
try {
|
|
2733
3121
|
await runtime.load();
|
|
2734
3122
|
for (const tool of runtime.getMcpTools()) {
|
|
2735
|
-
server.addTool({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema }, (input) => tool.
|
|
3123
|
+
server.addTool({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema }, (input) => runtime.runMcpTool(tool.name, input));
|
|
2736
3124
|
}
|
|
2737
3125
|
server.addStartupHook(() => runtime.runMcpStartup());
|
|
2738
3126
|
}
|
|
@@ -2749,7 +3137,7 @@ function printUsage() {
|
|
|
2749
3137
|
["Do things", ["save", "note", "recall", "search"]],
|
|
2750
3138
|
["See things", ["status", "doctor", "dashboard"]],
|
|
2751
3139
|
["Advanced", ["write", "read", "context", "scratchpad", "distil", "sync"]],
|
|
2752
|
-
["Setup", ["setup", "install-skills", "install-hooks", "completion"]],
|
|
3140
|
+
["Setup", ["setup", "install-skills", "install-hooks", "completion", "uninstall"]],
|
|
2753
3141
|
["Pro", ["pro", "learn"]],
|
|
2754
3142
|
];
|
|
2755
3143
|
const knownGroupCommands = new Set(groups.flatMap(([, list]) => list));
|
|
@@ -2850,7 +3238,7 @@ async function main() {
|
|
|
2850
3238
|
process.stdout.isTTY &&
|
|
2851
3239
|
!json &&
|
|
2852
3240
|
command &&
|
|
2853
|
-
!["init", "help", "version", "doctor", "status", "completion", "hook", "serve"].includes(command) &&
|
|
3241
|
+
!["init", "help", "version", "doctor", "status", "completion", "hook", "serve", "uninstall"].includes(command) &&
|
|
2854
3242
|
!fs.existsSync(getMemoryDir())) {
|
|
2855
3243
|
console.log(colorize("It looks like this is your first run — no memory directory yet.", "yellow"));
|
|
2856
3244
|
if (await promptYesNo("Run agent-memory init to set things up?", true)) {
|
|
@@ -2962,6 +3350,9 @@ async function main() {
|
|
|
2962
3350
|
case "uninstall-hooks":
|
|
2963
3351
|
cmdUninstallHooks(flags);
|
|
2964
3352
|
break;
|
|
3353
|
+
case "uninstall":
|
|
3354
|
+
await cmdUninstall(flags);
|
|
3355
|
+
break;
|
|
2965
3356
|
case "hook": {
|
|
2966
3357
|
const sub = positional[0];
|
|
2967
3358
|
if (sub !== "session-start" && sub !== "user-prompt-submit" && sub !== "stop") {
|
|
@@ -2984,6 +3375,13 @@ async function main() {
|
|
|
2984
3375
|
const layer = readHookMode() === "per-turn" ? "stable" : undefined;
|
|
2985
3376
|
await cmdContext(layer ? { "no-search": true, layer } : { "no-search": true });
|
|
2986
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
|
+
}
|
|
2987
3385
|
const cache = readUpgradeCache();
|
|
2988
3386
|
if (cache) {
|
|
2989
3387
|
const status = await checkForUpgrades({
|
|
@@ -2991,7 +3389,7 @@ async function main() {
|
|
|
2991
3389
|
pluginCurrent: cache.pluginCurrent,
|
|
2992
3390
|
cacheOnly: true,
|
|
2993
3391
|
});
|
|
2994
|
-
const notice = formatUpgradeNotice(status);
|
|
3392
|
+
const notice = formatUpgradeNotice(status, cache);
|
|
2995
3393
|
if (notice)
|
|
2996
3394
|
console.error(notice);
|
|
2997
3395
|
}
|
|
@@ -3037,7 +3435,7 @@ async function main() {
|
|
|
3037
3435
|
await cmdPro(flags, positional);
|
|
3038
3436
|
break;
|
|
3039
3437
|
case "upgrade":
|
|
3040
|
-
await cmdUpgrade(flags);
|
|
3438
|
+
await cmdUpgrade(flags, positional);
|
|
3041
3439
|
break;
|
|
3042
3440
|
case "serve":
|
|
3043
3441
|
await cmdServe(flags);
|