myagentmemory 0.4.13 → 0.4.15

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/src/cli.ts CHANGED
@@ -12,14 +12,21 @@
12
12
  * search — Search via qmd
13
13
  * init — Create dirs, detect qmd, setup collection
14
14
  * status — Show config, qmd status, file counts
15
+ * completion — Install or print shell completion
16
+ * install-hooks — Install managed session-start hooks
17
+ * uninstall-hooks — Remove managed session-start hooks
18
+ * plugin — Discover and bootstrap optional official plugins
15
19
  *
16
20
  * Global flags:
17
21
  * --dir <path> Override memory directory
18
22
  * --json Machine-readable JSON output
19
23
  */
20
24
 
25
+ import { spawn } from "node:child_process";
21
26
  import * as fs from "node:fs";
22
27
 
28
+ import { type CompletionShell, detectCompletionShell, generateCompletion, installCompletion } from "./completions.js";
29
+
23
30
  import {
24
31
  _setBaseDir,
25
32
  buildMemoryContext,
@@ -60,6 +67,13 @@ import {
60
67
  topicPath,
61
68
  uninstallSkills,
62
69
  } from "./core.js";
70
+ import { detectHookAgents, type HookAgentKey, installHooks, uninstallHooks } from "./hooks.js";
71
+ import {
72
+ createDefaultPluginBootstrap,
73
+ PluginBootstrapFailure,
74
+ type PluginBootstrapResultV1,
75
+ } from "./plugin-bootstrap.js";
76
+ import { InstalledPluginRuntimeV1 } from "./plugin-runtime.js";
63
77
 
64
78
  declare const __VERSION__: string;
65
79
 
@@ -148,6 +162,112 @@ function exitError(message: string, json: boolean): never {
148
162
  process.exit(1);
149
163
  }
150
164
 
165
+ function openExternalUrl(url: string): boolean {
166
+ let parsed: URL;
167
+ try {
168
+ parsed = new URL(url);
169
+ } catch {
170
+ return false;
171
+ }
172
+ if (parsed.protocol !== "https:") return false;
173
+ try {
174
+ const child =
175
+ process.platform === "darwin"
176
+ ? spawn("open", [parsed.toString()], { detached: true, stdio: "ignore" })
177
+ : process.platform === "win32"
178
+ ? spawn("explorer.exe", [parsed.toString()], { detached: true, stdio: "ignore" })
179
+ : spawn("xdg-open", [parsed.toString()], { detached: true, stdio: "ignore" });
180
+ child.unref();
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ function printProOverview(installed: boolean): void {
188
+ console.log("");
189
+ console.log("AgentMemory Pro includes:");
190
+ console.log(" Session Intelligence Recall decisions and context across Pi, Codex, and Claude Code sessions.");
191
+ console.log(" Guided Learning Turn repeated corrections into reviewable, reversible memory.");
192
+ console.log(" Local Web Console Inspect memories, activity, health, and settings in your browser.");
193
+ console.log("");
194
+ console.log("Your session content stays on this device.");
195
+ console.log("");
196
+ if (installed) {
197
+ console.log("Try it:");
198
+ console.log(' agent-memory recall "what did we decide about authentication?"');
199
+ console.log(" agent-memory learn");
200
+ console.log(" agent-memory web");
201
+ } else {
202
+ console.log("Start your Pro beta:");
203
+ console.log(" agent-memory plugin install");
204
+ }
205
+ }
206
+
207
+ function printPluginResult(result: PluginBootstrapResultV1, json: boolean, allowBrowser: boolean): void {
208
+ if (json) {
209
+ output(result, true);
210
+ } else if (result.command === "plugin.list" && result.plugins) {
211
+ for (const plugin of result.plugins) {
212
+ const state = plugin.available ? "available" : plugin.installed ? plugin.entitlement : "not installed";
213
+ console.log(`${plugin.name}: ${state}`);
214
+ }
215
+ printProOverview(Boolean(result.bundle));
216
+ } else {
217
+ const version = result.bundle?.version ? ` ${result.bundle.version}` : "";
218
+ let showOverview = false;
219
+ switch (result.result) {
220
+ case "installed":
221
+ console.log(`AgentMemory Pro${version} installed.`);
222
+ showOverview = true;
223
+ break;
224
+ case "upgraded":
225
+ console.log(`AgentMemory Pro upgraded to${version}.`);
226
+ showOverview = true;
227
+ break;
228
+ case "current":
229
+ console.log(
230
+ result.bundle
231
+ ? `AgentMemory Pro${version} is installed and ready.`
232
+ : "AgentMemory Pro is not installed.",
233
+ );
234
+ showOverview = Boolean(result.bundle);
235
+ break;
236
+ case "update_available":
237
+ console.log(`AgentMemory Pro${version} has an update available.`);
238
+ break;
239
+ case "uninstalled":
240
+ console.log("AgentMemory Pro executable components were removed. Memory and billing state were preserved.");
241
+ break;
242
+ case "not_installed":
243
+ console.log("AgentMemory Pro is not installed.");
244
+ console.log("Run: agent-memory plugin install");
245
+ break;
246
+ case "auth_required":
247
+ console.log(
248
+ "Run this command in an interactive terminal to enter an email and activate free daily access.",
249
+ );
250
+ break;
251
+ case "renewal_required":
252
+ console.log("Renew AgentMemory Pro to continue using paid capabilities.");
253
+ break;
254
+ default:
255
+ console.log(result.error?.message ?? "AgentMemory Pro is currently unavailable.");
256
+ }
257
+ if (showOverview) printProOverview(true);
258
+ }
259
+
260
+ if (result.nextAction) {
261
+ if (allowBrowser && openExternalUrl(result.nextAction.url)) {
262
+ if (!json) console.log("Opened the AgentMemory account website.");
263
+ } else if (!json) {
264
+ console.log(`Open: ${result.nextAction.url}`);
265
+ }
266
+ if (!json && result.nextAction.userCode) console.log(`Code: ${result.nextAction.userCode}`);
267
+ }
268
+ if (!result.ok) process.exitCode = 1;
269
+ }
270
+
151
271
  // ---------------------------------------------------------------------------
152
272
  // Commands
153
273
  // ---------------------------------------------------------------------------
@@ -508,6 +628,90 @@ function cmdInstallSkills(flags: Record<string, string | boolean>) {
508
628
  }
509
629
  }
510
630
 
631
+ async function promptYesNo(question: string, defaultYes: boolean): Promise<boolean> {
632
+ const readline = await import("node:readline/promises");
633
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
634
+ try {
635
+ const answer = (await rl.question(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `)).trim().toLowerCase();
636
+ if (!answer) return defaultYes;
637
+ return answer === "y" || answer === "yes";
638
+ } finally {
639
+ rl.close();
640
+ }
641
+ }
642
+
643
+ async function cmdInstallHooks(flags: Record<string, string | boolean>): Promise<void> {
644
+ const json = hasFlag(flags, "json");
645
+ const requested = getFlag(flags, "only");
646
+ const requestedKeys = requested ? new Set(requested.split(",").map((value) => value.trim())) : null;
647
+ const { homeDir, targets } = detectHookAgents();
648
+ if (!homeDir) exitError("Home directory not found.", json);
649
+ const eligible = targets.filter(
650
+ (target) => target.supported && target.detected && (!requestedKeys || requestedKeys.has(target.key)),
651
+ );
652
+ const selected = new Set<HookAgentKey>();
653
+ const applyAll = hasFlag(flags, "yes") || hasFlag(flags, "all") || !process.stdin.isTTY;
654
+ for (const target of eligible) {
655
+ if (applyAll || (await promptYesNo(`Install SessionStart hook for ${target.label}?`, true)))
656
+ selected.add(target.key);
657
+ }
658
+ const report = installHooks(selected);
659
+ if (!report.ok) exitError(report.error ?? "install failed", json);
660
+ if (json) return output(report, true);
661
+ if (!report.results.length) return output("No eligible agents. Nothing to install.", false);
662
+ for (const result of report.results) {
663
+ console.log(
664
+ result.installed
665
+ ? `Installed ${result.label} hook: ${result.path}`
666
+ : `Skipped ${result.label} (${result.reason ?? "unknown"})`,
667
+ );
668
+ }
669
+ }
670
+
671
+ function cmdUninstallHooks(flags: Record<string, string | boolean>): void {
672
+ const json = hasFlag(flags, "json");
673
+ const only = getFlag(flags, "only");
674
+ const agents = only ? new Set(only.split(",").map((value) => value.trim()) as HookAgentKey[]) : undefined;
675
+ const report = uninstallHooks(agents);
676
+ if (!report.ok) exitError(report.error ?? "uninstall failed", json);
677
+ if (json) {
678
+ output(report, true);
679
+ return;
680
+ }
681
+ for (const result of report.results) {
682
+ console.log(
683
+ result.installed
684
+ ? `Uninstalled ${result.label}: ${result.path}`
685
+ : `Skipped ${result.label} (${result.reason ?? "unknown"})`,
686
+ );
687
+ }
688
+ }
689
+
690
+ function cmdCompletion(flags: Record<string, string | boolean>, positional: string[]): void {
691
+ const requestedShell = positional[0];
692
+ const shells: CompletionShell[] = ["bash", "zsh", "fish", "powershell"];
693
+ if (requestedShell && !shells.includes(requestedShell as CompletionShell))
694
+ exitError(
695
+ `Unsupported shell '${requestedShell}'. Choose bash, zsh, fish, or powershell.`,
696
+ hasFlag(flags, "json"),
697
+ );
698
+ const shell = (requestedShell as CompletionShell | undefined) ?? detectCompletionShell();
699
+ if (!shell)
700
+ exitError("Could not detect your shell. Specify bash, zsh, fish, or powershell.", hasFlag(flags, "json"));
701
+ if (hasFlag(flags, "stdout")) {
702
+ process.stdout.write(generateCompletion(shell));
703
+ return;
704
+ }
705
+ const result = installCompletion(shell);
706
+ if (hasFlag(flags, "json")) {
707
+ output(result, true);
708
+ return;
709
+ }
710
+ console.log(`Installed ${shell} completion: ${result.completionPath}`);
711
+ if (result.profilePath)
712
+ console.log(`${result.profileUpdated ? "Configured" : "Already configured"}: ${result.profilePath}`);
713
+ }
714
+
511
715
  async function cmdSync(flags: Record<string, string | boolean>) {
512
716
  const json = hasFlag(flags, "json");
513
717
 
@@ -602,6 +806,18 @@ async function cmdInit(flags: Record<string, string | boolean>) {
602
806
  console.log(` qmd not found — search features unavailable.`);
603
807
  console.log(` Install: bun install -g https://github.com/tobi/qmd`);
604
808
  }
809
+ if (process.stdout.isTTY) {
810
+ try {
811
+ const plugin = await createDefaultPluginBootstrap(VERSION).list();
812
+ if (plugin.result === "not_installed") {
813
+ console.log("");
814
+ console.log("Optional: AgentMemory Pro adds session recall and a local Web Console.");
815
+ console.log("Run: agent-memory plugin install");
816
+ }
817
+ } catch {
818
+ // Commercial discovery must never make core initialization fail.
819
+ }
820
+ }
605
821
  }
606
822
  }
607
823
 
@@ -650,6 +866,21 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
650
866
  }
651
867
 
652
868
  const embedMode = getQmdEmbedMode();
869
+ let officialPlugin: { installed: boolean; result: string; entitlement: string } = {
870
+ installed: false,
871
+ result: "unavailable",
872
+ entitlement: "missing",
873
+ };
874
+ try {
875
+ const plugin = await createDefaultPluginBootstrap(VERSION).status();
876
+ officialPlugin = {
877
+ installed: Boolean(plugin.bundle),
878
+ result: plugin.result,
879
+ entitlement: plugin.entitlement.state,
880
+ };
881
+ } catch {
882
+ // Commercial status must never make core status fail.
883
+ }
653
884
 
654
885
  if (json) {
655
886
  output(
@@ -674,6 +905,7 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
674
905
  embeddings,
675
906
  },
676
907
  embedMode,
908
+ officialPlugin,
677
909
  },
678
910
  true,
679
911
  );
@@ -723,6 +955,11 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
723
955
  } else {
724
956
  console.log("qmd: not installed");
725
957
  }
958
+ if (!officialPlugin.installed) {
959
+ console.log("");
960
+ console.log("Optional official plugins: not installed");
961
+ console.log(" run: agent-memory plugin install");
962
+ }
726
963
  }
727
964
  }
728
965
 
@@ -753,6 +990,116 @@ async function cmdDistil(flags: Record<string, string | boolean>) {
753
990
  }
754
991
  }
755
992
 
993
+ function printPluginUsage(): void {
994
+ console.log(`agent-memory plugin — optional official plugins
995
+
996
+ Usage:
997
+ agent-memory plugin [list]
998
+ agent-memory plugin status
999
+ agent-memory plugin install [--channel stable] [--no-browser]
1000
+ agent-memory plugin update [--channel stable]
1001
+ agent-memory plugin uninstall --yes
1002
+ agent-memory plugin manage [--no-browser]
1003
+
1004
+ The public core remains fully usable without AgentMemory Pro. Interactive install
1005
+ opens a loopback website for email activation and a configurable free daily
1006
+ agent-session allowance. Memory and session content stay on this device.`);
1007
+ }
1008
+
1009
+ function pluginCommandFailure(command: string, error: unknown): PluginBootstrapResultV1 {
1010
+ return {
1011
+ schemaVersion: 1,
1012
+ command: `plugin.${command}`,
1013
+ ok: false,
1014
+ result: "unavailable",
1015
+ bundle: null,
1016
+ entitlement: {
1017
+ plan: null,
1018
+ state: "missing",
1019
+ features: [],
1020
+ capabilities: {},
1021
+ },
1022
+ nextAction: null,
1023
+ error: {
1024
+ code: error instanceof PluginBootstrapFailure ? error.code : "plugin_command_failed",
1025
+ message: error instanceof Error ? error.message : String(error),
1026
+ ...(error instanceof PluginBootstrapFailure && error.retryable ? { retryable: true } : {}),
1027
+ },
1028
+ };
1029
+ }
1030
+
1031
+ async function cmdPlugin(flags: Record<string, string | boolean>, positional: string[]): Promise<void> {
1032
+ const json = hasFlag(flags, "json");
1033
+ const subcommand = positional[0] ?? "list";
1034
+ if (subcommand === "help" || hasFlag(flags, "help")) {
1035
+ printPluginUsage();
1036
+ return;
1037
+ }
1038
+ const channel = getFlag(flags, "channel") ?? "stable";
1039
+ if (channel !== "stable") {
1040
+ printPluginResult(
1041
+ pluginCommandFailure(
1042
+ subcommand,
1043
+ new PluginBootstrapFailure("channel_invalid", "--channel supports only 'stable'"),
1044
+ ),
1045
+ json,
1046
+ false,
1047
+ );
1048
+ return;
1049
+ }
1050
+ const allowBrowser = !json && !hasFlag(flags, "no-browser") && Boolean(process.stdin.isTTY && process.stdout.isTTY);
1051
+ const manager = createDefaultPluginBootstrap(VERSION);
1052
+
1053
+ let result: PluginBootstrapResultV1;
1054
+ try {
1055
+ switch (subcommand) {
1056
+ case "list":
1057
+ result = await manager.list();
1058
+ break;
1059
+ case "status":
1060
+ result = await manager.status(channel);
1061
+ break;
1062
+ case "install":
1063
+ result = await manager.install({ channel, allowAuthentication: allowBrowser });
1064
+ break;
1065
+ case "update":
1066
+ result = await manager.update({ channel, allowAuthentication: false });
1067
+ break;
1068
+ case "uninstall":
1069
+ if (!hasFlag(flags, "yes")) {
1070
+ const status = await manager.status(channel);
1071
+ result = {
1072
+ ...status,
1073
+ command: "plugin.uninstall",
1074
+ ok: false,
1075
+ result: "unavailable",
1076
+ error: {
1077
+ code: "confirmation_required",
1078
+ message: "Re-run with --yes to remove AgentMemory Pro executable components",
1079
+ },
1080
+ };
1081
+ break;
1082
+ }
1083
+ result = await manager.uninstall();
1084
+ break;
1085
+ case "manage":
1086
+ result = await manager.manage();
1087
+ break;
1088
+ default:
1089
+ result = pluginCommandFailure(
1090
+ subcommand,
1091
+ new PluginBootstrapFailure(
1092
+ "unknown_plugin_command",
1093
+ `Unknown plugin command: ${subcommand}. Available bootstrap commands: list, status, install, update, uninstall, manage.`,
1094
+ ),
1095
+ );
1096
+ }
1097
+ } catch (error) {
1098
+ result = pluginCommandFailure(subcommand, error);
1099
+ }
1100
+ printPluginResult(result, json, allowBrowser && (subcommand === "install" || subcommand === "manage"));
1101
+ }
1102
+
756
1103
  // ---------------------------------------------------------------------------
757
1104
  // Usage
758
1105
  // ---------------------------------------------------------------------------
@@ -776,6 +1123,10 @@ Commands:
776
1123
  sync Re-index and embed all files (requires qmd)
777
1124
  init Initialize memory directory and qmd collection
778
1125
  status Show configuration and status (--probe for a live embeddings check)
1126
+ completion Install or print shell completion
1127
+ install-hooks Install managed SessionStart hooks
1128
+ uninstall-hooks Remove only managed SessionStart hooks
1129
+ plugin Discover, install, update, or remove optional official plugins
779
1130
 
780
1131
  Global flags:
781
1132
  --dir <path> Override memory directory
@@ -798,7 +1149,11 @@ Examples:
798
1149
  agent-memory distil --dry-run
799
1150
  agent-memory context --query "database choice"
800
1151
  agent-memory sync
801
- agent-memory status --json`);
1152
+ agent-memory status --json
1153
+ agent-memory completion zsh
1154
+ agent-memory install-hooks --yes
1155
+ agent-memory plugin status
1156
+ agent-memory plugin install`);
802
1157
  }
803
1158
 
804
1159
  // ---------------------------------------------------------------------------
@@ -820,7 +1175,7 @@ async function main() {
820
1175
  return;
821
1176
  }
822
1177
 
823
- if (!command || command === "help" || hasFlag(flags, "help")) {
1178
+ if (!command || command === "help" || (hasFlag(flags, "help") && command !== "plugin")) {
824
1179
  printUsage();
825
1180
  return;
826
1181
  }
@@ -860,8 +1215,53 @@ async function main() {
860
1215
  case "status":
861
1216
  await cmdStatus(flags);
862
1217
  break;
863
- default:
864
- exitError(`Unknown command: ${command}. Run 'agent-memory help' for usage.`, json);
1218
+ case "completion":
1219
+ cmdCompletion(flags, positional);
1220
+ break;
1221
+ case "install-hooks":
1222
+ await cmdInstallHooks(flags);
1223
+ break;
1224
+ case "uninstall-hooks":
1225
+ cmdUninstallHooks(flags);
1226
+ break;
1227
+ case "hook": {
1228
+ if (positional[0] !== "session-start") exitError("hook requires 'session-start'", json);
1229
+ const agent = getFlag(flags, "agent");
1230
+ if (!agent) exitError("hook session-start requires --agent", json);
1231
+ await cmdContext({ "no-search": true });
1232
+ try {
1233
+ const decision = await new InstalledPluginRuntimeV1({ coreVersion: VERSION }).runSessionStart({
1234
+ host: agent,
1235
+ cwd: process.cwd(),
1236
+ signal: new AbortController().signal,
1237
+ });
1238
+ if (decision?.state === "exhausted")
1239
+ console.error(`AgentMemory free session allowance resets at ${decision.resetAt}`);
1240
+ } catch {
1241
+ // Paid SessionStart work must never make public-core context unavailable.
1242
+ }
1243
+ break;
1244
+ }
1245
+ case "plugin":
1246
+ await cmdPlugin(flags, positional);
1247
+ break;
1248
+ default: {
1249
+ const controller = new AbortController();
1250
+ const abort = () => controller.abort();
1251
+ process.once("SIGINT", abort);
1252
+ try {
1253
+ const result = await new InstalledPluginRuntimeV1({ coreVersion: VERSION }).run(command, {
1254
+ args: positional,
1255
+ flags,
1256
+ signal: controller.signal,
1257
+ });
1258
+ if (!result) exitError(`Unknown command: ${command}. Run 'agent-memory help' for usage.`, json);
1259
+ if (!result.ok) exitError(result.error?.message ?? `Plugin command ${command} failed`, json);
1260
+ output(result.data ?? { ok: true }, json);
1261
+ } finally {
1262
+ process.removeListener("SIGINT", abort);
1263
+ }
1264
+ }
865
1265
  }
866
1266
  }
867
1267