pi-nocturne-memory 1.0.1 → 1.0.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.
Files changed (2) hide show
  1. package/extensions/index.ts +165 -36
  2. package/package.json +1 -1
@@ -2,48 +2,62 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Text } from "@earendil-works/pi-tui";
3
3
  import { Type } from "typebox";
4
4
 
5
- const BOOT_URIS = [
6
- "system://boot",
7
- "system://recent/5",
8
- "system://glossary",
9
- ];
5
+ const MCP_URL = "https://nocturne-memory.slahser.com/mcp";
6
+ const MCP_AUTH = "Bearer lsU70u_QHX8MJz4j7HZGEK79UGTEnA1RTyiALs7YltU";
10
7
 
11
- export default function (pi: ExtensionAPI) {
8
+ const BOOT_URIS = ["system://boot", "system://recent/5", "system://glossary"];
9
+
10
+ async function callMCP(method: string, params: Record<string, unknown>): Promise<any> {
11
+ const resp = await fetch(MCP_URL, {
12
+ method: "POST",
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ Accept: "application/json, text/event-stream",
16
+ Authorization: MCP_AUTH,
17
+ },
18
+ body: JSON.stringify({ jsonrpc: "2.0", id: method + Date.now(), method, params }),
19
+ });
20
+
21
+ const text = await resp.text();
22
+ const lines = text.split("\n");
23
+ for (const line of lines) {
24
+ if (line.startsWith("data: ")) {
25
+ try {
26
+ return JSON.parse(line.slice(6));
27
+ } catch {
28
+ // continue
29
+ }
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function extractText(data: any): string {
36
+ return data?.result?.content?.[0]?.text ?? "";
37
+ }
38
+
39
+ export function registerNocturneMemoryTools(pi: ExtensionAPI): void {
12
40
  pi.registerTool({
13
41
  name: "nocturne_boot",
14
42
  label: "Boot Memory",
15
43
  description:
16
- "Call this at the start of every new session. Loads core memories, recent context, and glossary. This is your self-discipline startup protocol — call it before doing anything else.",
44
+ "Call at session start. Loads core memories, recent context, and glossary. Self-discipline startup protocol.",
17
45
  promptGuidelines: [
18
- "MUST call this at the start of every new session before any other work.",
19
- "This loads your core identity, recent context, and trigger glossary.",
20
- "After booting, check if any disclosure conditions are triggered by the current conversation.",
46
+ "MUST call at session start before any other work.",
47
+ "Loads core identity, recent context, and trigger glossary.",
21
48
  ],
22
49
  parameters: Type.Object({}),
23
50
 
24
51
  async execute(_toolCallId, _params, _signal, onUpdate) {
25
- onUpdate?.({
26
- content: [{ type: "text", text: "🌙 Booting Nocturne Memory..." }],
27
- details: { phase: "booting" },
28
- });
52
+ onUpdate?.({ content: [{ type: "text", text: "🌙 Booting..." }], details: { phase: "booting" } });
29
53
 
30
54
  const results: string[] = [];
31
55
  const errors: string[] = [];
32
56
 
33
57
  for (const uri of BOOT_URIS) {
34
58
  try {
35
- const resp = await fetch(`http://localhost:3000/mcp`, {
36
- method: "POST",
37
- headers: { "Content-Type": "application/json" },
38
- body: JSON.stringify({
39
- jsonrpc: "2.0",
40
- id: uri,
41
- method: "tools/call",
42
- params: { name: "read_memory", arguments: { uri } },
43
- }),
44
- });
45
- const data = await resp.json();
46
- if (data.result?.content?.[0]?.text) {
59
+ const data = await callMCP("tools/call", { name: "read_memory", arguments: { uri } });
60
+ if (data?.result?.content?.[0]?.text) {
47
61
  results.push(`=== ${uri} ===\n${data.result.content[0].text}`);
48
62
  }
49
63
  } catch (err) {
@@ -52,26 +66,141 @@ export default function (pi: ExtensionAPI) {
52
66
  }
53
67
 
54
68
  if (results.length === 0 && errors.length > 0) {
55
- return {
56
- content: [{ type: "text", text: `❌ Failed to boot memory:\n${errors.join("\n")}` }],
57
- details: { error: errors.join("\n") },
58
- };
69
+ return { content: [{ type: "text", text: `❌ ${errors.join("\n")}` }], details: { error: errors.join("\n") } };
59
70
  }
60
71
 
61
- return {
62
- content: [{ type: "text", text: results.join("\n\n---\n\n") }],
63
- details: { booted: results.length, failed: errors.length },
64
- };
72
+ return { content: [{ type: "text", text: results.join("\n\n---\n\n") }], details: { booted: results.length } };
65
73
  },
66
74
 
67
75
  renderCall(_args, theme) {
68
- return new Text(theme.fg("toolTitle", theme.bold("🌙 Boot Memory")), 0, 0);
76
+ return new Text(theme.fg("toolTitle", theme.bold("🌙 Boot")), 0, 0);
69
77
  },
70
78
 
71
79
  renderResult(result, _options, theme) {
72
80
  const d = result.details as { error?: string; booted?: number } | undefined;
73
81
  if (d?.error) return new Text(theme.fg("error", "❌ Boot failed"), 0, 0);
74
- return new Text(theme.fg("success", `✓ ${d?.booted ?? 0} memory nodes loaded`), 0, 0);
82
+ return new Text(theme.fg("success", `✓ ${d?.booted ?? 0} nodes loaded`), 0, 0);
83
+ },
84
+ });
85
+
86
+ pi.registerTool({
87
+ name: "nocturne_read",
88
+ label: "Read Memory",
89
+ description: "Read a memory by URI. Use system:// URIs or memory paths like core://agent.",
90
+ parameters: Type.Object({
91
+ uri: Type.String({ description: "Memory URI (e.g., core://agent, system://boot)" }),
92
+ }),
93
+
94
+ async execute(_toolCallId, params) {
95
+ const data = await callMCP("tools/call", { name: "read_memory", arguments: { uri: params.uri } });
96
+ return { content: [{ type: "text", text: extractText(data) || "No content" }] };
97
+ },
98
+
99
+ renderCall(args, theme) {
100
+ return new Text(theme.fg("toolTitle", theme.bold("📖 ")) + theme.fg("accent", (args.uri as string) ?? ""), 0, 0);
101
+ },
102
+
103
+ renderResult(result, _options, theme) {
104
+ const text = (result.content?.[0] as any)?.text ?? "";
105
+ return new Text(theme.fg("success", `✓ ${text.length} chars`), 0, 0);
106
+ },
107
+ });
108
+
109
+ pi.registerTool({
110
+ name: "nocturne_search",
111
+ label: "Search Memory",
112
+ description: "Search memories by keywords in path and content.",
113
+ parameters: Type.Object({
114
+ query: Type.String({ description: "Search keywords" }),
115
+ domain: Type.Optional(Type.String({ description: "Domain filter (e.g., core, writer)" })),
116
+ }),
117
+
118
+ async execute(_toolCallId, params) {
119
+ const args: Record<string, unknown> = { query: params.query };
120
+ if (params.domain) args.domain = params.domain;
121
+ const data = await callMCP("tools/call", { name: "search_memory", arguments: args });
122
+ return { content: [{ type: "text", text: extractText(data) || "No results" }] };
123
+ },
124
+
125
+ renderCall(args, theme) {
126
+ return new Text(theme.fg("toolTitle", theme.bold("🔍 ")) + theme.fg("accent", (args.query as string) ?? ""), 0, 0);
127
+ },
128
+
129
+ renderResult(result, _options, theme) {
130
+ const text = (result.content?.[0] as any)?.text ?? "";
131
+ const lines = text.split("\n").filter(Boolean).length;
132
+ return new Text(theme.fg("success", `✓ ${lines} results`), 0, 0);
133
+ },
134
+ });
135
+
136
+ pi.registerTool({
137
+ name: "nocturne_create",
138
+ label: "Create Memory",
139
+ description: "Create a new memory node. Include [Baseline], [Deviation], [Result], [Reusable judgment] for behavior records.",
140
+ parameters: Type.Object({
141
+ parent_uri: Type.String({ description: "Parent URI (e.g., core://)" }),
142
+ content: Type.String({ description: "Memory content (Markdown supported)" }),
143
+ priority: Type.Number({ description: "Priority (0=highest)", default: 2 }),
144
+ disclosure: Type.String({ description: "When to recall this memory (e.g., 'When discussing X')" }),
145
+ title: Type.Optional(Type.String({ description: "Path name (a-z, 0-9, _, -)" })),
146
+ }),
147
+
148
+ async execute(_toolCallId, params) {
149
+ const data = await callMCP("tools/call", {
150
+ name: "create_memory",
151
+ arguments: {
152
+ parent_uri: params.parent_uri,
153
+ content: params.content,
154
+ priority: params.priority,
155
+ disclosure: params.disclosure,
156
+ ...(params.title ? { title: params.title } : {}),
157
+ },
158
+ });
159
+ return { content: [{ type: "text", text: extractText(data) || "Created" }] };
160
+ },
161
+
162
+ renderCall(args, theme) {
163
+ return new Text(theme.fg("toolTitle", theme.bold("➕ Create")), 0, 0);
164
+ },
165
+
166
+ renderResult(result, _options, theme) {
167
+ const text = (result.content?.[0] as any)?.text ?? "";
168
+ return new Text(theme.fg("success", text.slice(0, 100)), 0, 0);
169
+ },
170
+ });
171
+
172
+ pi.registerTool({
173
+ name: "nocturne_update",
174
+ label: "Update Memory",
175
+ description: "Update existing memory. Use patch mode (old_string/new_string) or append mode.",
176
+ parameters: Type.Object({
177
+ uri: Type.String({ description: "Memory URI to update" }),
178
+ old_string: Type.Optional(Type.String({ description: "Text to replace (patch mode)" })),
179
+ new_string: Type.Optional(Type.String({ description: "Replacement text (patch mode)" })),
180
+ append: Type.Optional(Type.String({ description: "Text to append (append mode)" })),
181
+ priority: Type.Optional(Type.Number({ description: "New priority" })),
182
+ disclosure: Type.Optional(Type.String({ description: "New disclosure condition" })),
183
+ }),
184
+
185
+ async execute(_toolCallId, params) {
186
+ const args: Record<string, unknown> = { uri: params.uri };
187
+ if (params.old_string) args.old_string = params.old_string;
188
+ if (params.new_string) args.new_string = params.new_string;
189
+ if (params.append) args.append = params.append;
190
+ if (params.priority !== undefined) args.priority = params.priority;
191
+ if (params.disclosure) args.disclosure = params.disclosure;
192
+
193
+ const data = await callMCP("tools/call", { name: "update_memory", arguments: args });
194
+ return { content: [{ type: "text", text: extractText(data) || "Updated" }] };
195
+ },
196
+
197
+ renderCall(args, theme) {
198
+ return new Text(theme.fg("toolTitle", theme.bold("✏️ Update")), 0, 0);
199
+ },
200
+
201
+ renderResult(result, _options, theme) {
202
+ const text = (result.content?.[0] as any)?.text ?? "";
203
+ return new Text(theme.fg("success", text.slice(0, 100)), 0, 0);
75
204
  },
76
205
  });
77
206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-nocturne-memory",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Nocturne Memory extension for Pi — automated memory management with SessionStart boot protocol",
5
5
  "license": "MIT",
6
6
  "type": "module",