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/README.md +5 -2
- package/dist/cli-spec.d.ts +1 -1
- package/dist/cli-spec.js +15 -3
- package/dist/cli.js +436 -42
- 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-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/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) =>
|
|
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
|
-
|
|
1084
|
-
|
|
1085
|
-
return
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1477
|
-
|
|
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
|
|
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
|
|
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
|
|
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);
|
package/dist/completions.d.ts
CHANGED
|
@@ -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[];
|