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/completions.js
CHANGED
|
@@ -395,6 +395,29 @@ function ensureProfileBlock(filePath, lines) {
|
|
|
395
395
|
fs.writeFileSync(filePath, updated, { mode: 0o600 });
|
|
396
396
|
return true;
|
|
397
397
|
}
|
|
398
|
+
/** Reverse of {@link ensureProfileBlock}: strips the marker block, if present, from the given file. */
|
|
399
|
+
function removeProfileBlock(filePath) {
|
|
400
|
+
const start = "# >>> agent-memory completion >>>";
|
|
401
|
+
const end = "# <<< agent-memory completion <<<";
|
|
402
|
+
if (!fs.existsSync(filePath))
|
|
403
|
+
return false;
|
|
404
|
+
const current = fs.readFileSync(filePath, "utf8");
|
|
405
|
+
const startIndex = current.indexOf(start);
|
|
406
|
+
const endIndex = startIndex === -1 ? -1 : current.indexOf(end, startIndex);
|
|
407
|
+
if (startIndex === -1 || endIndex === -1)
|
|
408
|
+
return false;
|
|
409
|
+
const updated = (current.slice(0, startIndex) + current.slice(endIndex + end.length)).replace(/\n{3,}/g, "\n\n");
|
|
410
|
+
if (updated === current)
|
|
411
|
+
return false;
|
|
412
|
+
fs.writeFileSync(filePath, updated, { mode: 0o600 });
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
function removeCompletionFile(filePath) {
|
|
416
|
+
if (!fs.existsSync(filePath))
|
|
417
|
+
return false;
|
|
418
|
+
fs.unlinkSync(filePath);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
398
421
|
export function installCompletion(shell, options = {}) {
|
|
399
422
|
const homeDir = options.homeDir ?? os.homedir();
|
|
400
423
|
const platform = options.platform ?? process.platform;
|
|
@@ -433,3 +456,31 @@ export function installCompletion(shell, options = {}) {
|
|
|
433
456
|
]);
|
|
434
457
|
return { shell, completionPath, profilePath, profileUpdated };
|
|
435
458
|
}
|
|
459
|
+
function uninstallShellCompletion(shell, homeDir, platform) {
|
|
460
|
+
const completionDir = path.join(homeDir, ".config", "agent-memory", "completions");
|
|
461
|
+
if (shell === "fish") {
|
|
462
|
+
const completionPath = path.join(homeDir, ".config", "fish", "completions", "agent-memory.fish");
|
|
463
|
+
return { shell, completionPath, removed: removeCompletionFile(completionPath), profileUpdated: false };
|
|
464
|
+
}
|
|
465
|
+
const extension = shell === "powershell" ? "ps1" : shell;
|
|
466
|
+
const completionPath = path.join(completionDir, `agent-memory.${extension}`);
|
|
467
|
+
const removed = removeCompletionFile(completionPath);
|
|
468
|
+
if (shell === "bash") {
|
|
469
|
+
const profilePath = path.join(homeDir, ".bashrc");
|
|
470
|
+
return { shell, completionPath, removed, profilePath, profileUpdated: removeProfileBlock(profilePath) };
|
|
471
|
+
}
|
|
472
|
+
if (shell === "zsh") {
|
|
473
|
+
const profilePath = path.join(homeDir, ".zshrc");
|
|
474
|
+
return { shell, completionPath, removed, profilePath, profileUpdated: removeProfileBlock(profilePath) };
|
|
475
|
+
}
|
|
476
|
+
const profilePath = platform === "win32"
|
|
477
|
+
? path.join(homeDir, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
|
|
478
|
+
: path.join(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1");
|
|
479
|
+
return { shell, completionPath, removed, profilePath, profileUpdated: removeProfileBlock(profilePath) };
|
|
480
|
+
}
|
|
481
|
+
/** Reverse of {@link installCompletion} across every supported shell. */
|
|
482
|
+
export function uninstallCompletion(options = {}) {
|
|
483
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
484
|
+
const platform = options.platform ?? process.platform;
|
|
485
|
+
return ["bash", "zsh", "fish", "powershell"].map((shell) => uninstallShellCompletion(shell, homeDir, platform));
|
|
486
|
+
}
|
package/dist/core.js
CHANGED
|
@@ -872,6 +872,17 @@ export function installSkills() {
|
|
|
872
872
|
destDir: path.join(homeDir, ".cursor", "skills", "agent-memory"),
|
|
873
873
|
homeMarker: path.join(homeDir, ".cursor"),
|
|
874
874
|
},
|
|
875
|
+
{
|
|
876
|
+
label: "Qoder skill",
|
|
877
|
+
srcDir: path.join(skillsDir, "qoder"),
|
|
878
|
+
destDir: path.join(homeDir, ".qoder", "skills", "agent-memory"),
|
|
879
|
+
homeMarker: path.join(homeDir, ".qoder"),
|
|
880
|
+
detectFiles: [
|
|
881
|
+
path.join(homeDir, ".qoder", "settings.json"),
|
|
882
|
+
path.join(homeDir, ".qoder", "settings.local.json"),
|
|
883
|
+
],
|
|
884
|
+
detectCommand: "qoder",
|
|
885
|
+
},
|
|
875
886
|
{
|
|
876
887
|
label: "Agent CLI skill",
|
|
877
888
|
srcDir: path.join(skillsDir, "agent"),
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { type HookMode } from "./core.js";
|
|
3
|
+
type ExecFileSyncFn = typeof execFileSync;
|
|
4
|
+
/** Override the execFileSync implementation used by hook installers (for testing). */
|
|
5
|
+
export declare function _setHookExecForTest(fn: ExecFileSyncFn): void;
|
|
6
|
+
/** Reset the execFileSync implementation to the real one. */
|
|
7
|
+
export declare function _resetHookExecForTest(): void;
|
|
2
8
|
/** Override the detected home directory in deterministic tests. */
|
|
3
9
|
export declare function _setHookHomeDirForTest(directory: string | null): void;
|
|
4
|
-
export type HookAgentKey = "claude" | "codex" | "cursor" | "opencode" | "pi";
|
|
10
|
+
export type HookAgentKey = "claude" | "codex" | "cursor" | "opencode" | "pi" | "qoder";
|
|
5
11
|
export interface HookTargetInfo {
|
|
6
12
|
key: HookAgentKey;
|
|
7
13
|
label: string;
|
|
@@ -52,6 +58,13 @@ export interface InstallHooksReport {
|
|
|
52
58
|
results: HookInstallResult[];
|
|
53
59
|
error?: string;
|
|
54
60
|
}
|
|
61
|
+
export interface PiMemoryState {
|
|
62
|
+
lastAttemptAt: string;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
detail: string;
|
|
65
|
+
}
|
|
66
|
+
/** Read the last recorded `pi install npm:pi-memory` attempt, if any. Never throws. */
|
|
67
|
+
export declare function getPiMemoryState(): PiMemoryState | null;
|
|
55
68
|
export declare function installHooks(agents: Set<HookAgentKey>, mode?: HookMode): InstallHooksReport;
|
|
56
69
|
export interface UninstallHooksReport {
|
|
57
70
|
ok: boolean;
|
|
@@ -60,3 +73,4 @@ export interface UninstallHooksReport {
|
|
|
60
73
|
error?: string;
|
|
61
74
|
}
|
|
62
75
|
export declare function uninstallHooks(agents?: Set<HookAgentKey>): UninstallHooksReport;
|
|
76
|
+
export {};
|
package/dist/hooks.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
|
-
import { writeHookMode } from "./core.js";
|
|
5
|
+
import { getMemoryDir, writeHookMode } from "./core.js";
|
|
6
|
+
let execFileSyncFn = execFileSync;
|
|
7
|
+
/** Override the execFileSync implementation used by hook installers (for testing). */
|
|
8
|
+
export function _setHookExecForTest(fn) {
|
|
9
|
+
execFileSyncFn = fn;
|
|
10
|
+
}
|
|
11
|
+
/** Reset the execFileSync implementation to the real one. */
|
|
12
|
+
export function _resetHookExecForTest() {
|
|
13
|
+
execFileSyncFn = execFileSync;
|
|
14
|
+
}
|
|
5
15
|
let homeDirOverride = null;
|
|
6
16
|
/** Override the detected home directory in deterministic tests. */
|
|
7
17
|
export function _setHookHomeDirForTest(directory) {
|
|
@@ -80,12 +90,22 @@ function hookTargets(homeDir) {
|
|
|
80
90
|
},
|
|
81
91
|
{
|
|
82
92
|
key: "pi",
|
|
83
|
-
label: "pi",
|
|
93
|
+
label: "pi (via pi-memory)",
|
|
84
94
|
homeMarker: path.join(homeDir, ".pi"),
|
|
85
95
|
detectFiles: [],
|
|
86
96
|
detectCommand: "pi",
|
|
87
|
-
supported:
|
|
88
|
-
|
|
97
|
+
supported: true,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "qoder",
|
|
101
|
+
label: "Qoder",
|
|
102
|
+
homeMarker: path.join(homeDir, ".qoder"),
|
|
103
|
+
detectFiles: [
|
|
104
|
+
path.join(homeDir, ".qoder", "settings.json"),
|
|
105
|
+
path.join(homeDir, ".qoder", "settings.local.json"),
|
|
106
|
+
],
|
|
107
|
+
detectCommand: "qoder",
|
|
108
|
+
supported: true,
|
|
89
109
|
},
|
|
90
110
|
];
|
|
91
111
|
}
|
|
@@ -143,6 +163,14 @@ export function isHookInstalled(homeDir, key) {
|
|
|
143
163
|
const instructionsPath = path.join(homeDir, ".agent-memory", "hooks", "opencode.md");
|
|
144
164
|
return list.includes(instructionsPath);
|
|
145
165
|
}
|
|
166
|
+
if (key === "pi") {
|
|
167
|
+
// Live filesystem state is authoritative — pi-memory can be installed or
|
|
168
|
+
// removed outside agent-memory (manually, or via `pi uninstall`) at any
|
|
169
|
+
// time, so a recorded delegate attempt must never override what's
|
|
170
|
+
// actually on disk. The state file is diagnostic-only (surfaced
|
|
171
|
+
// separately in `doctor`'s detail text), not a substitute for this check.
|
|
172
|
+
return fs.existsSync(path.join(homeDir, ".pi", "agent", "memory"));
|
|
173
|
+
}
|
|
146
174
|
}
|
|
147
175
|
catch {
|
|
148
176
|
return false;
|
|
@@ -232,6 +260,68 @@ function writeJson(filePath, data) {
|
|
|
232
260
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
233
261
|
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
234
262
|
}
|
|
263
|
+
function piMemoryStatePath() {
|
|
264
|
+
return path.join(getMemoryDir(), "pi-memory-state.json");
|
|
265
|
+
}
|
|
266
|
+
function writePiMemoryState(ok, detail) {
|
|
267
|
+
try {
|
|
268
|
+
writeJson(piMemoryStatePath(), { lastAttemptAt: new Date().toISOString(), ok, detail });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// best-effort bookkeeping only — never block the actual install/uninstall result on this.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Read the last recorded `pi install npm:pi-memory` attempt, if any. Never throws. */
|
|
275
|
+
export function getPiMemoryState() {
|
|
276
|
+
try {
|
|
277
|
+
const filePath = piMemoryStatePath();
|
|
278
|
+
if (!fs.existsSync(filePath))
|
|
279
|
+
return null;
|
|
280
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
281
|
+
if (typeof parsed?.lastAttemptAt !== "string" || typeof parsed?.ok !== "boolean")
|
|
282
|
+
return null;
|
|
283
|
+
return { lastAttemptAt: parsed.lastAttemptAt, ok: parsed.ok, detail: String(parsed.detail ?? "") };
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function installPiMemoryDelegate(homeDir) {
|
|
290
|
+
const memoryPath = path.join(homeDir, ".pi", "agent", "memory");
|
|
291
|
+
try {
|
|
292
|
+
const stdout = execFileSyncFn("pi", ["install", "npm:pi-memory"], {
|
|
293
|
+
encoding: "utf-8",
|
|
294
|
+
timeout: 30_000,
|
|
295
|
+
});
|
|
296
|
+
const detail = typeof stdout === "string" ? stdout.trim() : "";
|
|
297
|
+
writePiMemoryState(true, detail);
|
|
298
|
+
return { key: "pi", label: "pi (via pi-memory)", installed: true, path: memoryPath, reason: detail || undefined };
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
const detail = err && typeof err === "object" && "stderr" in err && err.stderr
|
|
302
|
+
? String(err.stderr).trim()
|
|
303
|
+
: err instanceof Error
|
|
304
|
+
? err.message
|
|
305
|
+
: String(err);
|
|
306
|
+
writePiMemoryState(false, detail);
|
|
307
|
+
return { key: "pi", label: "pi (via pi-memory)", installed: false, reason: detail };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function uninstallPiMemoryDelegate(homeDir) {
|
|
311
|
+
// agent-memory never owns this install, so it never runs `pi uninstall pi-memory` — but
|
|
312
|
+
// silently doing nothing would let an `agent-memory uninstall` report read as "fully cleaned
|
|
313
|
+
// up" while pi-memory keeps running. Phrase the reason distinctly when it's actually still
|
|
314
|
+
// active so callers (and cmdUninstall's step detail) can surface that honestly.
|
|
315
|
+
const stillActive = fs.existsSync(path.join(homeDir, ".pi", "agent", "memory"));
|
|
316
|
+
return {
|
|
317
|
+
key: "pi",
|
|
318
|
+
label: "pi (via pi-memory)",
|
|
319
|
+
installed: false,
|
|
320
|
+
reason: stillActive
|
|
321
|
+
? "pi-memory left installed (not managed by agent-memory) — run `pi uninstall pi-memory` to remove it"
|
|
322
|
+
: "not installed",
|
|
323
|
+
};
|
|
324
|
+
}
|
|
235
325
|
/**
|
|
236
326
|
* Idempotently upsert the agent-memory-managed hook group for `eventKey`
|
|
237
327
|
* (SessionStart or UserPromptSubmit) with `command`. Returns `{ changed,
|
|
@@ -524,6 +614,93 @@ function installOpencodeInstructions(homeDir) {
|
|
|
524
614
|
writeJson(configPath, config);
|
|
525
615
|
return { key: "opencode", label: "opencode", installed: true, path: configPath, backup };
|
|
526
616
|
}
|
|
617
|
+
function installQoderHook(homeDir) {
|
|
618
|
+
const settingsPath = path.join(homeDir, ".qoder", "settings.json");
|
|
619
|
+
const backup = backupOnce(settingsPath);
|
|
620
|
+
const settings = readJsonConfig(settingsPath);
|
|
621
|
+
const hooks = settings.hooks ?? {};
|
|
622
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
623
|
+
const command = "agent-memory context";
|
|
624
|
+
let managed = 0;
|
|
625
|
+
let updated = 0;
|
|
626
|
+
for (const group of sessionStart) {
|
|
627
|
+
if (!group || typeof group !== "object")
|
|
628
|
+
continue;
|
|
629
|
+
const g = group;
|
|
630
|
+
const list = Array.isArray(g.hooks) ? g.hooks : [];
|
|
631
|
+
for (const hook of list) {
|
|
632
|
+
if (!hook || typeof hook !== "object")
|
|
633
|
+
continue;
|
|
634
|
+
const managedHook = hook;
|
|
635
|
+
if (managedHook[HOOK_MARKER_JSON] !== true)
|
|
636
|
+
continue;
|
|
637
|
+
managed++;
|
|
638
|
+
if (managedHook.command !== command) {
|
|
639
|
+
managedHook.command = command;
|
|
640
|
+
updated++;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (managed && !updated) {
|
|
645
|
+
return { key: "qoder", label: "Qoder", installed: false, path: settingsPath, reason: "already installed" };
|
|
646
|
+
}
|
|
647
|
+
if (updated) {
|
|
648
|
+
hooks.SessionStart = sessionStart;
|
|
649
|
+
settings.hooks = hooks;
|
|
650
|
+
writeJson(settingsPath, settings);
|
|
651
|
+
return { key: "qoder", label: "Qoder", installed: true, path: settingsPath, backup, reason: "updated" };
|
|
652
|
+
}
|
|
653
|
+
sessionStart.push({
|
|
654
|
+
hooks: [{ type: "command", command, [HOOK_MARKER_JSON]: true }],
|
|
655
|
+
});
|
|
656
|
+
hooks.SessionStart = sessionStart;
|
|
657
|
+
settings.hooks = hooks;
|
|
658
|
+
writeJson(settingsPath, settings);
|
|
659
|
+
return { key: "qoder", label: "Qoder", installed: true, path: settingsPath, backup };
|
|
660
|
+
}
|
|
661
|
+
function uninstallQoderHook(homeDir) {
|
|
662
|
+
const settingsPath = path.join(homeDir, ".qoder", "settings.json");
|
|
663
|
+
if (!fs.existsSync(settingsPath)) {
|
|
664
|
+
return { key: "qoder", label: "Qoder", installed: false, reason: "not installed" };
|
|
665
|
+
}
|
|
666
|
+
const settings = readJsonConfig(settingsPath);
|
|
667
|
+
const hooks = settings.hooks ?? {};
|
|
668
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
669
|
+
let removed = 0;
|
|
670
|
+
const filtered = sessionStart
|
|
671
|
+
.map((group) => {
|
|
672
|
+
if (!group || typeof group !== "object")
|
|
673
|
+
return group;
|
|
674
|
+
const g = { ...group };
|
|
675
|
+
const list = Array.isArray(g.hooks) ? g.hooks : [];
|
|
676
|
+
const kept = list.filter((h) => {
|
|
677
|
+
const isOurs = h && typeof h === "object" && h[HOOK_MARKER_JSON] === true;
|
|
678
|
+
if (isOurs)
|
|
679
|
+
removed++;
|
|
680
|
+
return !isOurs;
|
|
681
|
+
});
|
|
682
|
+
g.hooks = kept;
|
|
683
|
+
return g;
|
|
684
|
+
})
|
|
685
|
+
.filter((group) => {
|
|
686
|
+
if (!group || typeof group !== "object")
|
|
687
|
+
return true;
|
|
688
|
+
const g = group;
|
|
689
|
+
return Array.isArray(g.hooks) && g.hooks.length > 0;
|
|
690
|
+
});
|
|
691
|
+
if (removed === 0) {
|
|
692
|
+
return { key: "qoder", label: "Qoder", installed: false, reason: "not installed" };
|
|
693
|
+
}
|
|
694
|
+
hooks.SessionStart = filtered;
|
|
695
|
+
if (filtered.length === 0)
|
|
696
|
+
delete hooks.SessionStart;
|
|
697
|
+
if (Object.keys(hooks).length === 0)
|
|
698
|
+
delete settings.hooks;
|
|
699
|
+
else
|
|
700
|
+
settings.hooks = hooks;
|
|
701
|
+
writeJson(settingsPath, settings);
|
|
702
|
+
return { key: "qoder", label: "Qoder", installed: true, path: settingsPath };
|
|
703
|
+
}
|
|
527
704
|
export function installHooks(agents, mode = "per-turn") {
|
|
528
705
|
const { homeDir, targets } = detectHookAgents();
|
|
529
706
|
if (!homeDir) {
|
|
@@ -566,6 +743,10 @@ export function installHooks(agents, mode = "per-turn") {
|
|
|
566
743
|
result = installCursorHook(homeDir);
|
|
567
744
|
else if (target.key === "opencode")
|
|
568
745
|
result = installOpencodeInstructions(homeDir);
|
|
746
|
+
else if (target.key === "pi")
|
|
747
|
+
result = installPiMemoryDelegate(homeDir);
|
|
748
|
+
else if (target.key === "qoder")
|
|
749
|
+
result = installQoderHook(homeDir);
|
|
569
750
|
else
|
|
570
751
|
continue;
|
|
571
752
|
results.push(result);
|
|
@@ -711,7 +892,7 @@ export function uninstallHooks(agents) {
|
|
|
711
892
|
error: "Home directory not found. Set HOME (or USERPROFILE on Windows) and retry.",
|
|
712
893
|
};
|
|
713
894
|
}
|
|
714
|
-
const keys = ["claude", "codex", "cursor", "opencode"];
|
|
895
|
+
const keys = ["claude", "codex", "cursor", "opencode", "pi", "qoder"];
|
|
715
896
|
const results = [];
|
|
716
897
|
for (const key of keys) {
|
|
717
898
|
if (agents && !agents.has(key))
|
|
@@ -725,6 +906,10 @@ export function uninstallHooks(agents) {
|
|
|
725
906
|
results.push(uninstallCursorHook(homeDir));
|
|
726
907
|
else if (key === "opencode")
|
|
727
908
|
results.push(uninstallOpencodeInstructions(homeDir));
|
|
909
|
+
else if (key === "pi")
|
|
910
|
+
results.push(uninstallPiMemoryDelegate(homeDir));
|
|
911
|
+
else if (key === "qoder")
|
|
912
|
+
results.push(uninstallQoderHook(homeDir));
|
|
728
913
|
}
|
|
729
914
|
catch (err) {
|
|
730
915
|
results.push({
|
package/dist/plugin-service.js
CHANGED
|
@@ -402,14 +402,8 @@ export class AgentMemoryServiceBackend {
|
|
|
402
402
|
value.entitlement.state !== "active" ||
|
|
403
403
|
value.entitlement.capabilities.recall?.enabled !== true ||
|
|
404
404
|
!recallQuota ||
|
|
405
|
-
recallQuota.limit !== 20 ||
|
|
406
|
-
recallQuota.scope !== "device" ||
|
|
407
|
-
recallQuota.window !== "day" ||
|
|
408
405
|
value.entitlement.capabilities.learning?.enabled !== true ||
|
|
409
406
|
!learningQuota ||
|
|
410
|
-
learningQuota.limit !== 5 ||
|
|
411
|
-
learningQuota.scope !== "device" ||
|
|
412
|
-
learningQuota.window !== "day" ||
|
|
413
407
|
value.entitlement.capabilities["session-index"]?.enabled !== true ||
|
|
414
408
|
value.entitlement.capabilities["session-worker"]?.enabled !== false ||
|
|
415
409
|
value.entitlement.capabilities["web-console"]?.enabled !== true)
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Upgrade orchestration for the `agent-memory` CLI and its official Pro plugin bundle.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three consumers:
|
|
5
5
|
* 1. `agent-memory upgrade` — explicit user command; checks and (optionally) installs.
|
|
6
|
-
* 2. `agent-memory
|
|
6
|
+
* 2. `agent-memory upgrade --background` — detached, non-interactive; checks, then
|
|
7
|
+
* installs any target whose `readUpgradePolicy()` value is `"auto"` (the default).
|
|
8
|
+
* Spawned by `refreshUpgradeCacheBackground()` from `hook session-start`.
|
|
9
|
+
* 3. `agent-memory hook session-start` — passive notice from a 24h-cached record,
|
|
10
|
+
* including the outcome of the last `--background` auto-install attempt.
|
|
7
11
|
*
|
|
8
12
|
* Network calls always have a hard timeout and always fail closed (upgrade is a
|
|
9
|
-
* quality-of-life feature; a flaky registry must never break the CLI).
|
|
13
|
+
* quality-of-life feature; a flaky registry must never break the CLI). Same fail-closed
|
|
14
|
+
* contract applies to auto-install: a failed background install is recorded, never
|
|
15
|
+
* retried before the next cache refresh, and never thrown.
|
|
10
16
|
*/
|
|
11
17
|
import { type SpawnOptions } from "node:child_process";
|
|
12
18
|
export type InstallManager = "bun" | "npm" | "pnpm" | "yarn" | "unknown";
|
|
@@ -24,6 +30,17 @@ export interface UpgradeCache {
|
|
|
24
30
|
cliLatest: string | null;
|
|
25
31
|
pluginCurrent: string | null;
|
|
26
32
|
pluginLatest: string | null;
|
|
33
|
+
/** Outcome of the most recent `--background` auto-upgrade attempt, if any. */
|
|
34
|
+
cliAuto?: AutoUpgradeOutcome;
|
|
35
|
+
pluginAuto?: AutoUpgradeOutcome;
|
|
36
|
+
}
|
|
37
|
+
export interface AutoUpgradeOutcome {
|
|
38
|
+
at: string;
|
|
39
|
+
ok: boolean;
|
|
40
|
+
/** Version installed (ok) or the previous/current version (failure). */
|
|
41
|
+
version: string | null;
|
|
42
|
+
/** Failure reason; absent when ok. */
|
|
43
|
+
error?: string;
|
|
27
44
|
}
|
|
28
45
|
export interface UpgradeStatus {
|
|
29
46
|
cli: {
|
|
@@ -42,6 +59,24 @@ export interface UpgradeStatus {
|
|
|
42
59
|
export declare function readUpgradeCache(): UpgradeCache | null;
|
|
43
60
|
export declare function writeUpgradeCache(record: UpgradeCache): void;
|
|
44
61
|
export declare function isCacheFresh(record: UpgradeCache | null, now?: number): boolean;
|
|
62
|
+
export type UpgradePolicyValue = "off" | "notify" | "auto";
|
|
63
|
+
export interface UpgradePolicy {
|
|
64
|
+
cli: UpgradePolicyValue;
|
|
65
|
+
plugin: UpgradePolicyValue;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the persisted auto-upgrade policy.
|
|
69
|
+
* Precedence per target: `AGENT_MEMORY_AUTO_UPGRADE_{CLI,PLUGIN}` env var →
|
|
70
|
+
* `<memoryDir>/state/upgrade-policy.json` → default `"auto"`.
|
|
71
|
+
*
|
|
72
|
+
* `existed` tells callers whether the policy file was already on disk —
|
|
73
|
+
* used to fire a one-time "auto-upgrade is on" notice on first read.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readUpgradePolicy(): UpgradePolicy & {
|
|
76
|
+
existed: boolean;
|
|
77
|
+
};
|
|
78
|
+
/** Atomically persist the auto-upgrade policy. Merges with whatever is already on disk. */
|
|
79
|
+
export declare function writeUpgradePolicy(patch: Partial<UpgradePolicy>): UpgradePolicy;
|
|
45
80
|
/**
|
|
46
81
|
* Best-effort detection of how `myagentmemory` was installed. Path signatures
|
|
47
82
|
* are heuristic but cover the common managers. On no match we fall back to
|
|
@@ -58,8 +93,10 @@ export interface InstallResult {
|
|
|
58
93
|
export declare function runInstaller(method: InstallMethod, opts?: SpawnOptions): InstallResult;
|
|
59
94
|
/**
|
|
60
95
|
* Fire-and-forget: spawn a detached child that runs `agent-memory upgrade
|
|
61
|
-
* --
|
|
62
|
-
*
|
|
96
|
+
* --background --refresh --quiet` so the next session-start has a fresh
|
|
97
|
+
* cache. Unlike a plain check, `--background` also installs any target whose
|
|
98
|
+
* policy is `"auto"` (see `readUpgradePolicy`) — this is the one place
|
|
99
|
+
* auto-upgrade actually happens. Never awaits, never throws.
|
|
63
100
|
*/
|
|
64
101
|
export declare function refreshUpgradeCacheBackground(): void;
|
|
65
102
|
export interface CheckOptions {
|
|
@@ -77,4 +114,12 @@ export interface CheckOptions {
|
|
|
77
114
|
pluginUpgradeAvailable?: boolean;
|
|
78
115
|
}
|
|
79
116
|
export declare function checkForUpgrades(opts: CheckOptions): Promise<UpgradeStatus>;
|
|
80
|
-
|
|
117
|
+
/**
|
|
118
|
+
* `cache` (when passed) lets this distinguish a plain "notify" signal from the
|
|
119
|
+
* outcome of the last `--background` auto-install attempt for that target:
|
|
120
|
+
* - succeeded, but this process is running older code than what's on disk
|
|
121
|
+
* (e.g. a long-running `serve --mcp`) → "auto-upgraded, restart to use it"
|
|
122
|
+
* - failed → surface the error and point at the manual command
|
|
123
|
+
* - succeeded and already caught up (this process's own version matches) → silent
|
|
124
|
+
*/
|
|
125
|
+
export declare function formatUpgradeNotice(status: UpgradeStatus, cache?: UpgradeCache | null): string | null;
|
package/dist/upgrade.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Upgrade orchestration for the `agent-memory` CLI and its official Pro plugin bundle.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three consumers:
|
|
5
5
|
* 1. `agent-memory upgrade` — explicit user command; checks and (optionally) installs.
|
|
6
|
-
* 2. `agent-memory
|
|
6
|
+
* 2. `agent-memory upgrade --background` — detached, non-interactive; checks, then
|
|
7
|
+
* installs any target whose `readUpgradePolicy()` value is `"auto"` (the default).
|
|
8
|
+
* Spawned by `refreshUpgradeCacheBackground()` from `hook session-start`.
|
|
9
|
+
* 3. `agent-memory hook session-start` — passive notice from a 24h-cached record,
|
|
10
|
+
* including the outcome of the last `--background` auto-install attempt.
|
|
7
11
|
*
|
|
8
12
|
* Network calls always have a hard timeout and always fail closed (upgrade is a
|
|
9
|
-
* quality-of-life feature; a flaky registry must never break the CLI).
|
|
13
|
+
* quality-of-life feature; a flaky registry must never break the CLI). Same fail-closed
|
|
14
|
+
* contract applies to auto-install: a failed background install is recorded, never
|
|
15
|
+
* retried before the next cache refresh, and never thrown.
|
|
10
16
|
*/
|
|
11
17
|
import { spawn, spawnSync } from "node:child_process";
|
|
12
18
|
import * as fs from "node:fs";
|
|
@@ -50,6 +56,15 @@ async function fetchLatestFromNpm(fetchImpl = globalThis.fetch) {
|
|
|
50
56
|
function upgradeCachePath() {
|
|
51
57
|
return path.join(getMemoryDir(), "state", "upgrade-check.json");
|
|
52
58
|
}
|
|
59
|
+
function isValidAutoOutcome(value) {
|
|
60
|
+
if (typeof value !== "object" || value === null)
|
|
61
|
+
return false;
|
|
62
|
+
const candidate = value;
|
|
63
|
+
return (typeof candidate.at === "string" &&
|
|
64
|
+
typeof candidate.ok === "boolean" &&
|
|
65
|
+
(candidate.version === null || typeof candidate.version === "string") &&
|
|
66
|
+
(candidate.error === undefined || typeof candidate.error === "string"));
|
|
67
|
+
}
|
|
53
68
|
export function readUpgradeCache() {
|
|
54
69
|
try {
|
|
55
70
|
const raw = fs.readFileSync(upgradeCachePath(), "utf-8");
|
|
@@ -69,6 +84,8 @@ export function readUpgradeCache() {
|
|
|
69
84
|
cliLatest: parsed.cliLatest ?? null,
|
|
70
85
|
pluginCurrent: parsed.pluginCurrent ?? null,
|
|
71
86
|
pluginLatest: parsed.pluginLatest ?? null,
|
|
87
|
+
cliAuto: isValidAutoOutcome(parsed.cliAuto) ? parsed.cliAuto : undefined,
|
|
88
|
+
pluginAuto: isValidAutoOutcome(parsed.pluginAuto) ? parsed.pluginAuto : undefined,
|
|
72
89
|
};
|
|
73
90
|
}
|
|
74
91
|
catch {
|
|
@@ -93,6 +110,60 @@ export function isCacheFresh(record, now = Date.now()) {
|
|
|
93
110
|
return false;
|
|
94
111
|
return now - checked < CACHE_TTL_MS;
|
|
95
112
|
}
|
|
113
|
+
const UPGRADE_POLICY_FILENAME = "upgrade-policy.json";
|
|
114
|
+
const UPGRADE_POLICY_DEFAULT = { cli: "auto", plugin: "auto" };
|
|
115
|
+
function upgradePolicyPath() {
|
|
116
|
+
return path.join(getMemoryDir(), "state", UPGRADE_POLICY_FILENAME);
|
|
117
|
+
}
|
|
118
|
+
function isPolicyValue(value) {
|
|
119
|
+
return value === "off" || value === "notify" || value === "auto";
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the persisted auto-upgrade policy.
|
|
123
|
+
* Precedence per target: `AGENT_MEMORY_AUTO_UPGRADE_{CLI,PLUGIN}` env var →
|
|
124
|
+
* `<memoryDir>/state/upgrade-policy.json` → default `"auto"`.
|
|
125
|
+
*
|
|
126
|
+
* `existed` tells callers whether the policy file was already on disk —
|
|
127
|
+
* used to fire a one-time "auto-upgrade is on" notice on first read.
|
|
128
|
+
*/
|
|
129
|
+
export function readUpgradePolicy() {
|
|
130
|
+
let stored = {};
|
|
131
|
+
let existed = false;
|
|
132
|
+
try {
|
|
133
|
+
const raw = fs.readFileSync(upgradePolicyPath(), "utf-8");
|
|
134
|
+
const parsed = JSON.parse(raw);
|
|
135
|
+
if (isPolicyValue(parsed.cli) || isPolicyValue(parsed.plugin)) {
|
|
136
|
+
stored = parsed;
|
|
137
|
+
existed = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch { }
|
|
141
|
+
const envCli = process.env.AGENT_MEMORY_AUTO_UPGRADE_CLI;
|
|
142
|
+
const envPlugin = process.env.AGENT_MEMORY_AUTO_UPGRADE_PLUGIN;
|
|
143
|
+
return {
|
|
144
|
+
cli: isPolicyValue(envCli) ? envCli : isPolicyValue(stored.cli) ? stored.cli : UPGRADE_POLICY_DEFAULT.cli,
|
|
145
|
+
plugin: isPolicyValue(envPlugin)
|
|
146
|
+
? envPlugin
|
|
147
|
+
: isPolicyValue(stored.plugin)
|
|
148
|
+
? stored.plugin
|
|
149
|
+
: UPGRADE_POLICY_DEFAULT.plugin,
|
|
150
|
+
existed,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Atomically persist the auto-upgrade policy. Merges with whatever is already on disk. */
|
|
154
|
+
export function writeUpgradePolicy(patch) {
|
|
155
|
+
const current = readUpgradePolicy();
|
|
156
|
+
const next = {
|
|
157
|
+
cli: patch.cli ?? current.cli,
|
|
158
|
+
plugin: patch.plugin ?? current.plugin,
|
|
159
|
+
};
|
|
160
|
+
const target = upgradePolicyPath();
|
|
161
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
162
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
163
|
+
fs.writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
164
|
+
fs.renameSync(temporary, target);
|
|
165
|
+
return next;
|
|
166
|
+
}
|
|
96
167
|
// ---------------------------------------------------------------------------
|
|
97
168
|
// Install-method detection
|
|
98
169
|
// ---------------------------------------------------------------------------
|
|
@@ -163,8 +234,10 @@ export function runInstaller(method, opts = {}) {
|
|
|
163
234
|
// ---------------------------------------------------------------------------
|
|
164
235
|
/**
|
|
165
236
|
* Fire-and-forget: spawn a detached child that runs `agent-memory upgrade
|
|
166
|
-
* --
|
|
167
|
-
*
|
|
237
|
+
* --background --refresh --quiet` so the next session-start has a fresh
|
|
238
|
+
* cache. Unlike a plain check, `--background` also installs any target whose
|
|
239
|
+
* policy is `"auto"` (see `readUpgradePolicy`) — this is the one place
|
|
240
|
+
* auto-upgrade actually happens. Never awaits, never throws.
|
|
168
241
|
*/
|
|
169
242
|
export function refreshUpgradeCacheBackground() {
|
|
170
243
|
try {
|
|
@@ -172,7 +245,7 @@ export function refreshUpgradeCacheBackground() {
|
|
|
172
245
|
const script = process.argv[1];
|
|
173
246
|
if (!binary || !script)
|
|
174
247
|
return;
|
|
175
|
-
const child = spawn(binary, [script, "upgrade", "--
|
|
248
|
+
const child = spawn(binary, [script, "upgrade", "--background", "--refresh", "--quiet", "--json"], {
|
|
176
249
|
detached: true,
|
|
177
250
|
stdio: "ignore",
|
|
178
251
|
env: { ...process.env, AGENT_MEMORY_UPGRADE_BACKGROUND: "1" },
|
|
@@ -231,13 +304,40 @@ export async function checkForUpgrades(opts) {
|
|
|
231
304
|
fromCache,
|
|
232
305
|
};
|
|
233
306
|
}
|
|
234
|
-
|
|
307
|
+
/**
|
|
308
|
+
* `cache` (when passed) lets this distinguish a plain "notify" signal from the
|
|
309
|
+
* outcome of the last `--background` auto-install attempt for that target:
|
|
310
|
+
* - succeeded, but this process is running older code than what's on disk
|
|
311
|
+
* (e.g. a long-running `serve --mcp`) → "auto-upgraded, restart to use it"
|
|
312
|
+
* - failed → surface the error and point at the manual command
|
|
313
|
+
* - succeeded and already caught up (this process's own version matches) → silent
|
|
314
|
+
*/
|
|
315
|
+
export function formatUpgradeNotice(status, cache) {
|
|
235
316
|
const parts = [];
|
|
236
|
-
|
|
317
|
+
let needsManualRun = false;
|
|
318
|
+
if (cache?.cliAuto && !cache.cliAuto.ok) {
|
|
319
|
+
parts.push(`CLI auto-upgrade failed (${cache.cliAuto.error ?? "unknown error"})`);
|
|
320
|
+
needsManualRun = true;
|
|
321
|
+
}
|
|
322
|
+
else if (cache?.cliAuto?.ok && cache.cliAuto.version && cache.cliAuto.version !== status.cli.current) {
|
|
323
|
+
parts.push(`CLI auto-upgraded → ${cache.cliAuto.version} (restart any long-running agent-memory process to use it)`);
|
|
324
|
+
}
|
|
325
|
+
else if (status.cli.upgradeAvailable) {
|
|
237
326
|
parts.push(`CLI ${status.cli.current} → ${status.cli.latest ?? "new"}`);
|
|
238
|
-
|
|
327
|
+
needsManualRun = true;
|
|
328
|
+
}
|
|
329
|
+
if (cache?.pluginAuto && !cache.pluginAuto.ok) {
|
|
330
|
+
parts.push(`Pro auto-upgrade failed (${cache.pluginAuto.error ?? "unknown error"})`);
|
|
331
|
+
needsManualRun = true;
|
|
332
|
+
}
|
|
333
|
+
else if (cache?.pluginAuto?.ok && cache.pluginAuto.version && cache.pluginAuto.version !== status.plugin.current) {
|
|
334
|
+
parts.push(`Pro auto-upgraded → ${cache.pluginAuto.version}`);
|
|
335
|
+
}
|
|
336
|
+
else if (status.plugin.upgradeAvailable) {
|
|
239
337
|
parts.push(`Pro ${status.plugin.current ?? "?"} → ${status.plugin.latest ?? "new"}`);
|
|
338
|
+
needsManualRun = true;
|
|
339
|
+
}
|
|
240
340
|
if (!parts.length)
|
|
241
341
|
return null;
|
|
242
|
-
return `agent-memory:
|
|
342
|
+
return `agent-memory: ${parts.join("; ")}${needsManualRun ? ". Run: agent-memory upgrade" : ""}`;
|
|
243
343
|
}
|
|
@@ -267,7 +267,7 @@ An install or upgrade must:
|
|
|
267
267
|
|
|
268
268
|
Failure before activation leaves the previous version active. Failure immediately after activation restores the previous receipt. Concurrent installers do not interleave. The core never invokes package-manager lifecycle scripts or elevates privileges.
|
|
269
269
|
|
|
270
|
-
Uninstall removes executable versions, the active receipt, contributed skills, and managed hooks. It does not remove `MEMORY.md`, daily logs, topics, scratchpad items, source session logs, plugin-created review data, or billing state.
|
|
270
|
+
Uninstall removes executable versions, the active receipt, contributed skills, and managed hooks. It does not remove `MEMORY.md`, daily logs, topics, scratchpad items, source session logs, plugin-created review data, or billing state. The top-level `agent-memory uninstall` command composes this with hook/skill/MCP/completion removal in one step; its explicit `--data` flag additionally deletes the memory directory and the entire plugin install root (bundles, receipts, and the activation credential) once the user opts in and confirms.
|
|
271
271
|
|
|
272
272
|
## Plugin host API v1
|
|
273
273
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myagentmemory",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "agentmemory (agent-memory) is persistent memory for coding agents (Claude Code, OpenAI Codex, Cursor, Agent) with qmd-powered semantic search across daily logs, long-term memory, and scratchpad",
|
|
5
5
|
"main": "./dist/core.js",
|
|
6
6
|
"types": "./dist/core.d.ts",
|