@schift-io/memory 0.1.0
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/AGENTS.md +28 -0
- package/CLAUDE.md +42 -0
- package/hooks/hooks.json +28 -0
- package/hooks/run-hook.cmd +23 -0
- package/hooks/session-start +50 -0
- package/mcp-server.js +195 -0
- package/package.json +32 -0
- package/references/architecture.md +115 -0
- package/scripts/auto-ingest-url.sh +22 -0
- package/scripts/init.sh +64 -0
- package/scripts/login.sh +68 -0
- package/skills/memory-save/SKILL.md +138 -0
- package/skills/memory-search/SKILL.md +83 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @schift-io/memory
|
|
2
|
+
|
|
3
|
+
> Second brain for Claude Code - saves conversations and web content to your local knowledge base.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
schift-memory/
|
|
9
|
+
CLAUDE.md # Main plugin docs
|
|
10
|
+
AGENTS.md # This file
|
|
11
|
+
hooks/
|
|
12
|
+
hooks.json # Claude Code hook config
|
|
13
|
+
run-hook.cmd # Hook dispatcher
|
|
14
|
+
session-start # Injects memory awareness
|
|
15
|
+
skills/
|
|
16
|
+
memory-save/ # Save URLs, notes, insights
|
|
17
|
+
memory-search/ # Search past knowledge
|
|
18
|
+
scripts/
|
|
19
|
+
auto-ingest-url.sh # Post-WebFetch auto-save
|
|
20
|
+
init.sh # Bootstrap local memory
|
|
21
|
+
references/
|
|
22
|
+
architecture.md # How it works under the hood
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Skills
|
|
26
|
+
|
|
27
|
+
- **memory-save**: Invoke when the user shares a URL or wants to save something
|
|
28
|
+
- **memory-search**: Invoke when the user needs past context or searches their knowledge base
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @schift-io/memory
|
|
2
|
+
|
|
3
|
+
Second brain plugin for Claude Code. Saves conversations, web content, and notes to Schift Cloud.
|
|
4
|
+
|
|
5
|
+
**Requires a Schift account. No local-only mode. No fallback keys.**
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
| Skill | Trigger |
|
|
10
|
+
|-------|---------|
|
|
11
|
+
| `memory-save` | URL shared, "save this", "remember this" |
|
|
12
|
+
| `memory-search` | "find that...", "what did we discuss about...", needs past context |
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @schift-io/memory login # sign up + API key
|
|
18
|
+
npx @schift-io/memory init # bootstrap cloud bucket
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What's on the user's machine
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
~/.schift/memory/
|
|
25
|
+
config/
|
|
26
|
+
auth.json # API key
|
|
27
|
+
sources/
|
|
28
|
+
web/ # Web page markdown (replica)
|
|
29
|
+
compact/
|
|
30
|
+
session/ # Conversation summaries (replica)
|
|
31
|
+
topic/ # Aggregated topics (replica)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Raw data replicas stay local. User owns their data.
|
|
35
|
+
Compute (embed, search, index) is Cloud-only.
|
|
36
|
+
|
|
37
|
+
## Schift Cloud API
|
|
38
|
+
|
|
39
|
+
- `POST /v1/memory/bootstrap` - Create user's bucket
|
|
40
|
+
- `POST /v1/memory/ingest-url` - Save a URL
|
|
41
|
+
- `POST /v1/memory/compact` - Save conversation summary
|
|
42
|
+
- `POST /v1/query` - Search (collection=localbucket)
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
|
|
10
|
+
"async": false
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PostToolUse": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "WebFetch",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/auto-ingest-url.sh\"",
|
|
22
|
+
"async": true
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hook dispatcher for schift-memory plugin
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
hook_name="${1:-}"
|
|
10
|
+
if [ -z "$hook_name" ]; then
|
|
11
|
+
echo '{"error": "no hook name provided"}' >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
hook_script="${SCRIPT_DIR}/${hook_name}"
|
|
16
|
+
if [ -x "$hook_script" ]; then
|
|
17
|
+
exec "$hook_script"
|
|
18
|
+
elif [ -f "$hook_script" ]; then
|
|
19
|
+
exec bash "$hook_script"
|
|
20
|
+
else
|
|
21
|
+
echo "{\"error\": \"hook not found: ${hook_name}\"}" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR="$HOME/.schift/memory/config"
|
|
8
|
+
AUTH_FILE="${CONFIG_DIR}/auth.json"
|
|
9
|
+
|
|
10
|
+
# --- Gate: no auth = no memory ---
|
|
11
|
+
if [ ! -f "$AUTH_FILE" ]; then
|
|
12
|
+
cat <<ENDJSON
|
|
13
|
+
{
|
|
14
|
+
"additionalContext": "<EXTREMELY_IMPORTANT>\nSchift Memory is installed but NOT activated.\n\nThe user needs a Schift account to use their second brain.\nWhen the user tries to save, search, or use any memory feature, tell them:\n\n\"Schift Memory requires a free account. Run this to get started:\"\n\n\`\`\`bash\nnpx @schift-io/memory login\n\`\`\`\n\nOr sign up at: https://schift.io/signup?ref=memory-plugin\n\nDo NOT attempt to bypass this. Do NOT use fallback keys. The API will reject unauthenticated requests.\n</EXTREMELY_IMPORTANT>"
|
|
15
|
+
}
|
|
16
|
+
ENDJSON
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Validate key is not empty/expired
|
|
21
|
+
api_key=$(python3 -c "import json; print(json.load(open('${AUTH_FILE}'))['api_key'])" 2>/dev/null || echo "")
|
|
22
|
+
if [ -z "$api_key" ]; then
|
|
23
|
+
cat <<ENDJSON
|
|
24
|
+
{
|
|
25
|
+
"additionalContext": "<EXTREMELY_IMPORTANT>\nSchift Memory auth is invalid or expired.\n\nTell the user to re-authenticate:\n\n\`\`\`bash\nnpx @schift-io/memory login\n\`\`\`\n\nOr sign up at: https://schift.io/signup?ref=memory-plugin\n</EXTREMELY_IMPORTANT>"
|
|
26
|
+
}
|
|
27
|
+
ENDJSON
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# --- Authenticated: inject skills ---
|
|
32
|
+
memory_save_content=$(cat "${PLUGIN_ROOT}/skills/memory-save/SKILL.md" 2>&1 || echo "Error reading memory-save skill")
|
|
33
|
+
|
|
34
|
+
escape_for_json() {
|
|
35
|
+
local s="$1"
|
|
36
|
+
s="${s//\\/\\\\}"
|
|
37
|
+
s="${s//\"/\\\"}"
|
|
38
|
+
s="${s//$'\n'/\\n}"
|
|
39
|
+
s="${s//$'\r'/\\r}"
|
|
40
|
+
s="${s//$'\t'/\\t}"
|
|
41
|
+
printf '%s' "$s"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
escaped_content=$(escape_for_json "$memory_save_content")
|
|
45
|
+
|
|
46
|
+
cat <<ENDJSON
|
|
47
|
+
{
|
|
48
|
+
"additionalContext": "<EXTREMELY_IMPORTANT>\nSchift Memory is active. Your second brain is ready.\n\n${escaped_content}\n\nWhen the user shares a URL, ALWAYS offer to save it to their knowledge base.\nWhen the user says 'save this', 'remember this', or similar, use the memory-save skill.\n\nAPI key is loaded from ~/.schift/memory/config/auth.json. Use it for all API calls.\n</EXTREMELY_IMPORTANT>"
|
|
49
|
+
}
|
|
50
|
+
ENDJSON
|
package/mcp-server.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Schift Memory MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Tools:
|
|
6
|
+
* save_url - Save a URL to your knowledge base
|
|
7
|
+
* save_note - Save a note/insight
|
|
8
|
+
* search - Search your knowledge base
|
|
9
|
+
* status - Check connection status
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
|
|
16
|
+
const AUTH_PATH = join(homedir(), '.schift', 'memory', 'config', 'auth.json');
|
|
17
|
+
|
|
18
|
+
function loadAuth() {
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(readFileSync(AUTH_PATH, 'utf-8'));
|
|
21
|
+
return { key: data.api_key, url: data.cloud_url || 'https://api.schift.io' };
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function apiCall(path, body) {
|
|
28
|
+
const auth = loadAuth();
|
|
29
|
+
if (!auth) throw new Error('Not authenticated. Run: npx @schift-io/memory login');
|
|
30
|
+
const resp = await fetch(`${auth.url}${path}`, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${auth.key}` },
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
});
|
|
35
|
+
if (!resp.ok) {
|
|
36
|
+
const err = await resp.text();
|
|
37
|
+
throw new Error(`API error ${resp.status}: ${err}`);
|
|
38
|
+
}
|
|
39
|
+
return resp.json();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// MCP stdio protocol
|
|
43
|
+
const TOOLS = [
|
|
44
|
+
{
|
|
45
|
+
name: 'save_url',
|
|
46
|
+
description: 'Save a URL to your Schift knowledge base. Fetches content, extracts markdown, embeds, and makes it searchable.',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
url: { type: 'string', description: 'The URL to save' },
|
|
51
|
+
domain: { type: 'string', enum: ['company','business','finance','decision','product','ops','research','reference'], description: 'Knowledge domain category', default: 'ops' },
|
|
52
|
+
title: { type: 'string', description: 'Optional title override' },
|
|
53
|
+
summary: { type: 'string', description: 'One-line summary' },
|
|
54
|
+
context: { type: 'string', description: 'Why this was investigated (conversation context)' },
|
|
55
|
+
findings: { type: 'string', description: 'Key findings from the content' },
|
|
56
|
+
},
|
|
57
|
+
required: ['url'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'save_note',
|
|
62
|
+
description: 'Save a note or conversation insight to your Schift knowledge base.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
content: { type: 'string', description: 'The content to save' },
|
|
67
|
+
domain: { type: 'string', enum: ['company','business','finance','decision','product','ops','research','reference'], default: 'business' },
|
|
68
|
+
topic: { type: 'string', description: 'Optional topic slug (e.g. product-positioning)' },
|
|
69
|
+
},
|
|
70
|
+
required: ['content'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'search_memory',
|
|
75
|
+
description: 'Search your Schift knowledge base for past conversations, saved URLs, notes, and documents.',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
query: { type: 'string', description: 'Natural language search query' },
|
|
80
|
+
top_k: { type: 'number', description: 'Number of results', default: 5 },
|
|
81
|
+
domain: { type: 'string', description: 'Filter by domain' },
|
|
82
|
+
},
|
|
83
|
+
required: ['query'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'memory_status',
|
|
88
|
+
description: 'Check Schift Memory connection status and account info.',
|
|
89
|
+
inputSchema: { type: 'object', properties: {} },
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
async function handleToolCall(name, args) {
|
|
94
|
+
switch (name) {
|
|
95
|
+
case 'save_url':
|
|
96
|
+
return apiCall('/v1/memory/ingest-url', {
|
|
97
|
+
url: args.url,
|
|
98
|
+
domain: args.domain || 'ops',
|
|
99
|
+
title: args.title,
|
|
100
|
+
summary: args.summary,
|
|
101
|
+
context: args.context,
|
|
102
|
+
findings: args.findings,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
case 'save_note':
|
|
106
|
+
return apiCall('/v1/memory/compact', {
|
|
107
|
+
session_id: `note_${Date.now()}`,
|
|
108
|
+
summary: args.content,
|
|
109
|
+
domain: args.domain || 'business',
|
|
110
|
+
topic: args.topic,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
case 'search_memory':
|
|
114
|
+
return apiCall('/v1/query', {
|
|
115
|
+
query: args.query,
|
|
116
|
+
collection: 'localbucket',
|
|
117
|
+
top_k: args.top_k || 5,
|
|
118
|
+
...(args.domain ? { filter: { domain: args.domain } } : {}),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
case 'memory_status': {
|
|
122
|
+
const auth = loadAuth();
|
|
123
|
+
if (!auth) return { status: 'not_authenticated', message: 'Run: npx @schift-io/memory login' };
|
|
124
|
+
try {
|
|
125
|
+
const resp = await fetch(`${auth.url}/health`);
|
|
126
|
+
return { status: 'connected', cloud_url: auth.url, healthy: resp.ok };
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { status: 'error', message: e.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- MCP stdio transport ---
|
|
138
|
+
function send(msg) {
|
|
139
|
+
const json = JSON.stringify(msg);
|
|
140
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let buffer = '';
|
|
144
|
+
process.stdin.setEncoding('utf-8');
|
|
145
|
+
process.stdin.on('data', (chunk) => {
|
|
146
|
+
buffer += chunk;
|
|
147
|
+
while (true) {
|
|
148
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
149
|
+
if (headerEnd === -1) break;
|
|
150
|
+
const header = buffer.slice(0, headerEnd);
|
|
151
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
152
|
+
if (!match) { buffer = buffer.slice(headerEnd + 4); continue; }
|
|
153
|
+
const len = parseInt(match[1], 10);
|
|
154
|
+
const bodyStart = headerEnd + 4;
|
|
155
|
+
if (buffer.length < bodyStart + len) break;
|
|
156
|
+
const body = buffer.slice(bodyStart, bodyStart + len);
|
|
157
|
+
buffer = buffer.slice(bodyStart + len);
|
|
158
|
+
try {
|
|
159
|
+
const msg = JSON.parse(body);
|
|
160
|
+
handleMessage(msg);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
send({ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error' }, id: null });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
async function handleMessage(msg) {
|
|
168
|
+
const { id, method, params } = msg;
|
|
169
|
+
|
|
170
|
+
if (method === 'initialize') {
|
|
171
|
+
send({ jsonrpc: '2.0', id, result: {
|
|
172
|
+
protocolVersion: '2024-11-05',
|
|
173
|
+
capabilities: { tools: {} },
|
|
174
|
+
serverInfo: { name: 'schift-memory', version: '0.1.0' },
|
|
175
|
+
}});
|
|
176
|
+
} else if (method === 'notifications/initialized') {
|
|
177
|
+
// no-op
|
|
178
|
+
} else if (method === 'tools/list') {
|
|
179
|
+
send({ jsonrpc: '2.0', id, result: { tools: TOOLS } });
|
|
180
|
+
} else if (method === 'tools/call') {
|
|
181
|
+
try {
|
|
182
|
+
const result = await handleToolCall(params.name, params.arguments || {});
|
|
183
|
+
send({ jsonrpc: '2.0', id, result: {
|
|
184
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
185
|
+
}});
|
|
186
|
+
} catch (e) {
|
|
187
|
+
send({ jsonrpc: '2.0', id, result: {
|
|
188
|
+
content: [{ type: 'text', text: `Error: ${e.message}` }],
|
|
189
|
+
isError: true,
|
|
190
|
+
}});
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown method: ${method}` } });
|
|
194
|
+
}
|
|
195
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@schift-io/memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Second brain for Claude Code - saves conversations and web content to Schift Cloud",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"schift-memory-mcp": "./mcp-server.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/schift-io/schift-memory"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude-code",
|
|
16
|
+
"mcp",
|
|
17
|
+
"second-brain",
|
|
18
|
+
"knowledge-base",
|
|
19
|
+
"rag",
|
|
20
|
+
"memory",
|
|
21
|
+
"schift"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"hooks",
|
|
25
|
+
"skills",
|
|
26
|
+
"scripts",
|
|
27
|
+
"references",
|
|
28
|
+
"mcp-server.js",
|
|
29
|
+
"CLAUDE.md",
|
|
30
|
+
"AGENTS.md"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Schift Memory Architecture
|
|
2
|
+
|
|
3
|
+
## Core Principle
|
|
4
|
+
|
|
5
|
+
**Compute는 전부 Cloud. Raw data 복제본은 유저 로컬에.**
|
|
6
|
+
|
|
7
|
+
- Embedding, vector search, LLM routing = Schift Cloud
|
|
8
|
+
- Engine 바이너리, 벡터 인덱스 = 유저 컴에 없음
|
|
9
|
+
- 원본 markdown (웹페이지, 세션 compact, 메모) = 유저 로컬에 복제본 보관
|
|
10
|
+
- Cloud가 진실의 원천, 로컬은 읽기 전용 복제
|
|
11
|
+
|
|
12
|
+
## Data Flow
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
User conversation (Claude Code)
|
|
16
|
+
|
|
|
17
|
+
v
|
|
18
|
+
[Hook: SessionStart] -- checks ~/.schift/memory/config/auth.json
|
|
19
|
+
|
|
|
20
|
+
|-- no auth --> "Sign up at schift.io/signup" (BLOCKED)
|
|
21
|
+
|
|
|
22
|
+
|-- auth OK --> inject memory skills
|
|
23
|
+
|
|
|
24
|
+
+---> User shares URL ---> memory-save skill
|
|
25
|
+
| |
|
|
26
|
+
| v
|
|
27
|
+
| WebFetch content (Claude Code built-in)
|
|
28
|
+
| |
|
|
29
|
+
| v
|
|
30
|
+
| POST api.schift.io/v1/local-memory/ingest-url
|
|
31
|
+
| |
|
|
32
|
+
| v
|
|
33
|
+
| Schift Cloud: fetch + extract + embed + store
|
|
34
|
+
| |
|
|
35
|
+
| v
|
|
36
|
+
| Response includes markdown --> save to ~/.schift/memory/sources/web/
|
|
37
|
+
|
|
|
38
|
+
+---> Session ends ---> compact hook
|
|
39
|
+
| |
|
|
40
|
+
| v
|
|
41
|
+
| POST api.schift.io/v1/local-memory/compact
|
|
42
|
+
| |
|
|
43
|
+
| v
|
|
44
|
+
| Schift Cloud: summarize + embed + store
|
|
45
|
+
| |
|
|
46
|
+
| v
|
|
47
|
+
| Response includes compact md --> save to ~/.schift/memory/compact/session/
|
|
48
|
+
|
|
|
49
|
+
+---> User searches ---> memory-search skill
|
|
50
|
+
|
|
|
51
|
+
v
|
|
52
|
+
POST api.schift.io/v1/query
|
|
53
|
+
|
|
|
54
|
+
v
|
|
55
|
+
Schift Cloud: Engine vector search
|
|
56
|
+
|
|
|
57
|
+
v
|
|
58
|
+
Return ranked results
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## What lives on the user's machine
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
~/.schift/memory/
|
|
65
|
+
config/
|
|
66
|
+
auth.json # API key (required)
|
|
67
|
+
sources/
|
|
68
|
+
web/ # Fetched web pages (markdown replica)
|
|
69
|
+
compact/
|
|
70
|
+
session/ # Conversation summaries (markdown replica)
|
|
71
|
+
topic/ # Aggregated topics (markdown replica)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Read-only replicas. The user owns their raw data.
|
|
75
|
+
If they delete it locally, Cloud still has the original.
|
|
76
|
+
If they cancel their account, they keep the markdown files.
|
|
77
|
+
|
|
78
|
+
## What lives on Schift Cloud (exclusively)
|
|
79
|
+
|
|
80
|
+
- Embedded vectors (Engine)
|
|
81
|
+
- Vector index
|
|
82
|
+
- Manifest / metadata
|
|
83
|
+
- Usage metering
|
|
84
|
+
- Search ranking
|
|
85
|
+
|
|
86
|
+
## Why this split
|
|
87
|
+
|
|
88
|
+
| Concern | Answer |
|
|
89
|
+
|---------|--------|
|
|
90
|
+
| Data ownership | User has markdown copies. No hostage situation. |
|
|
91
|
+
| Lock-in | Search/embed only works via Cloud. Can't self-host. |
|
|
92
|
+
| Portability | Markdown is universal. Export = just copy the folder. |
|
|
93
|
+
| Trust | "Your data is yours" = lower signup friction. |
|
|
94
|
+
| Revenue | Every search = API call = metered. Raw files don't search themselves. |
|
|
95
|
+
|
|
96
|
+
## Metadata Schema
|
|
97
|
+
|
|
98
|
+
Every document in the user's cloud bucket has:
|
|
99
|
+
|
|
100
|
+
| Field | Required | Values |
|
|
101
|
+
|-------|----------|--------|
|
|
102
|
+
| `source_type` | yes | `raw`, `session_compact`, `topic_synthesis` |
|
|
103
|
+
| `domain` | yes | `company`, `business`, `finance`, `decision`, `product`, `ops`, `research`, `reference` |
|
|
104
|
+
| `origin` | yes | `docs`, `memory`, `claude_history`, `external`, `web` |
|
|
105
|
+
| `updated_at` | yes | ISO 8601 UTC |
|
|
106
|
+
| `source_url` | no | Original URL (for web sources) |
|
|
107
|
+
| `title` | no | Document title |
|
|
108
|
+
| `session_id` | no | Claude session ID |
|
|
109
|
+
| `topic` | no | Topic slug |
|
|
110
|
+
|
|
111
|
+
## Search Priority
|
|
112
|
+
|
|
113
|
+
1. `topic_synthesis` - aggregated, most reliable
|
|
114
|
+
2. `session_compact` - conversation context
|
|
115
|
+
3. `raw` - original documents
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-WebFetch hook: detects URLs from tool output and offers to save
|
|
3
|
+
# This runs async after WebFetch calls - it queues the URL for next prompt
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCHIFT_API="${SCHIFT_API_URL:-http://localhost:8787}"
|
|
7
|
+
SCHIFT_KEY="${SCHIFT_API_KEY:-local}"
|
|
8
|
+
QUEUE_FILE="$HOME/.schift/memory/queue/pending-urls.jsonl"
|
|
9
|
+
|
|
10
|
+
# Read tool input from stdin (Claude Code passes hook context)
|
|
11
|
+
input=$(cat 2>/dev/null || echo "{}")
|
|
12
|
+
url=$(echo "$input" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('url',''))" 2>/dev/null || echo "")
|
|
13
|
+
|
|
14
|
+
if [ -z "$url" ] || [ "$url" = "None" ]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Queue the URL for the assistant to ask about
|
|
19
|
+
mkdir -p "$(dirname "$QUEUE_FILE")"
|
|
20
|
+
echo "{\"url\": \"${url}\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" >> "$QUEUE_FILE"
|
|
21
|
+
|
|
22
|
+
exit 0
|
package/scripts/init.sh
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Bootstrap schift-memory - requires Schift account
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCHIFT_CLOUD="https://api.schift.io"
|
|
6
|
+
CONFIG_DIR="$HOME/.schift/memory/config"
|
|
7
|
+
AUTH_FILE="${CONFIG_DIR}/auth.json"
|
|
8
|
+
|
|
9
|
+
echo ""
|
|
10
|
+
echo " Schift Memory - Your Second Brain for Claude Code"
|
|
11
|
+
echo " =================================================="
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# --- Step 1: Check auth ---
|
|
15
|
+
if [ ! -f "$AUTH_FILE" ]; then
|
|
16
|
+
echo " No Schift account found."
|
|
17
|
+
echo ""
|
|
18
|
+
echo " 1. Sign up (free): https://schift.io/signup?ref=memory-plugin"
|
|
19
|
+
echo " 2. Then run: npx @schift-io/memory login"
|
|
20
|
+
echo ""
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
api_key=$(python3 -c "import json; print(json.load(open('${AUTH_FILE}'))['api_key'])" 2>/dev/null || echo "")
|
|
25
|
+
if [ -z "$api_key" ]; then
|
|
26
|
+
echo " Auth file exists but API key is missing."
|
|
27
|
+
echo " Run: npx @schift-io/memory login"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# --- Step 2: Validate key against Schift Cloud ---
|
|
32
|
+
echo " Validating Schift account..."
|
|
33
|
+
cloud_url=$(python3 -c "import json; print(json.load(open('${AUTH_FILE}')).get('cloud_url','${SCHIFT_CLOUD}'))" 2>/dev/null || echo "${SCHIFT_CLOUD}")
|
|
34
|
+
validate=$(curl -sf -o /dev/null -w "%{http_code}" \
|
|
35
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
36
|
+
"${cloud_url}/v1/me" 2>/dev/null || echo "000")
|
|
37
|
+
|
|
38
|
+
if [ "$validate" != "200" ]; then
|
|
39
|
+
echo " API key is invalid or expired."
|
|
40
|
+
echo " Run: npx @schift-io/memory login"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
echo " Account verified."
|
|
45
|
+
|
|
46
|
+
# --- Step 3: Create local replica directories ---
|
|
47
|
+
echo " Setting up local replica..."
|
|
48
|
+
mkdir -p "$HOME/.schift/memory"/{config,sources/web,compact/session,compact/topic}
|
|
49
|
+
|
|
50
|
+
# --- Step 4: Bootstrap bucket on Schift Cloud ---
|
|
51
|
+
echo " Creating knowledge bucket on Schift Cloud..."
|
|
52
|
+
curl -sf -X POST "${cloud_url}/v1/local-memory/bootstrap" \
|
|
53
|
+
-H "Content-Type: application/json" \
|
|
54
|
+
-H "Authorization: Bearer ${api_key}" > /dev/null 2>&1 || true
|
|
55
|
+
|
|
56
|
+
echo ""
|
|
57
|
+
echo " Ready! Your second brain is active."
|
|
58
|
+
echo ""
|
|
59
|
+
echo " Cloud: Schift Cloud (embed, search, index)"
|
|
60
|
+
echo " Local: ~/.schift/memory/ (raw data replica)"
|
|
61
|
+
echo ""
|
|
62
|
+
echo " Save a URL: share any link in Claude Code"
|
|
63
|
+
echo " Search memory: ask Claude 'what did we discuss about X?'"
|
|
64
|
+
echo ""
|
package/scripts/login.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Schift Memory login - get API key from Schift Cloud
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
CONFIG_DIR="$HOME/.schift/memory/config"
|
|
6
|
+
AUTH_FILE="${CONFIG_DIR}/auth.json"
|
|
7
|
+
SCHIFT_CLOUD="https://api.schift.io"
|
|
8
|
+
|
|
9
|
+
echo ""
|
|
10
|
+
echo " Schift Memory Login"
|
|
11
|
+
echo " ==================="
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# Check if already logged in
|
|
15
|
+
if [ -f "$AUTH_FILE" ]; then
|
|
16
|
+
existing_key=$(python3 -c "import json; print(json.load(open('${AUTH_FILE}'))['api_key'])" 2>/dev/null || echo "")
|
|
17
|
+
if [ -n "$existing_key" ]; then
|
|
18
|
+
validate=$(curl -sf -o /dev/null -w "%{http_code}" \
|
|
19
|
+
-H "Authorization: Bearer ${existing_key}" \
|
|
20
|
+
"${SCHIFT_CLOUD}/v1/me" 2>/dev/null || echo "000")
|
|
21
|
+
if [ "$validate" = "200" ]; then
|
|
22
|
+
echo " Already logged in. Account is valid."
|
|
23
|
+
echo " To re-login, delete: ${AUTH_FILE}"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo " Enter your Schift API key."
|
|
30
|
+
echo " (Get one at: https://schift.io/signup?ref=memory-plugin)"
|
|
31
|
+
echo ""
|
|
32
|
+
printf " API Key: "
|
|
33
|
+
read -r api_key
|
|
34
|
+
|
|
35
|
+
if [ -z "$api_key" ]; then
|
|
36
|
+
echo " No key provided. Aborting."
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Validate
|
|
41
|
+
echo " Validating..."
|
|
42
|
+
validate=$(curl -sf -o /dev/null -w "%{http_code}" \
|
|
43
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
44
|
+
"${SCHIFT_CLOUD}/v1/me" 2>/dev/null || echo "000")
|
|
45
|
+
|
|
46
|
+
if [ "$validate" != "200" ]; then
|
|
47
|
+
echo " Invalid API key. Check your key and try again."
|
|
48
|
+
echo " Sign up: https://schift.io/signup?ref=memory-plugin"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Save
|
|
53
|
+
mkdir -p "$CONFIG_DIR"
|
|
54
|
+
cat > "$AUTH_FILE" << AUTHEOF
|
|
55
|
+
{
|
|
56
|
+
"api_key": "${api_key}",
|
|
57
|
+
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
58
|
+
"cloud_url": "${SCHIFT_CLOUD}"
|
|
59
|
+
}
|
|
60
|
+
AUTHEOF
|
|
61
|
+
chmod 600 "$AUTH_FILE"
|
|
62
|
+
|
|
63
|
+
echo " Logged in! Running setup..."
|
|
64
|
+
echo ""
|
|
65
|
+
|
|
66
|
+
# Auto-run init
|
|
67
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
68
|
+
exec "${SCRIPT_DIR}/init.sh"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-save
|
|
3
|
+
description: Save URLs, notes, and conversation insights to Schift knowledge bucket. Triggers on URL sharing, "save this", "remember this", or explicit save requests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schift Memory - Save
|
|
7
|
+
|
|
8
|
+
You have a knowledge base powered by Schift Cloud.
|
|
9
|
+
When the user shares content worth remembering, save it as a contextualized knowledge document.
|
|
10
|
+
|
|
11
|
+
## Auth check
|
|
12
|
+
|
|
13
|
+
Before ANY save operation, verify auth exists:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cat ~/.schift/memory/config/auth.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If missing or no `api_key`, STOP:
|
|
20
|
+
|
|
21
|
+
> Schift Memory requires a free account.
|
|
22
|
+
> Run: `npx @schift-io/memory login`
|
|
23
|
+
> Or sign up: https://schift.io/signup?ref=memory-plugin
|
|
24
|
+
|
|
25
|
+
Do NOT proceed without a valid API key. There is no local-only mode.
|
|
26
|
+
|
|
27
|
+
## When to activate
|
|
28
|
+
|
|
29
|
+
- User shares a URL (article, docs, report, blog post)
|
|
30
|
+
- User says "save this", "remember this", "store this"
|
|
31
|
+
- User shares important context that should persist across sessions
|
|
32
|
+
- After WebFetch, if the content seems valuable
|
|
33
|
+
|
|
34
|
+
## How to save a URL (the full flow)
|
|
35
|
+
|
|
36
|
+
This is NOT a simple bookmark. You produce a **contextualized knowledge document**.
|
|
37
|
+
|
|
38
|
+
### Step 1: Read auth
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
SCHIFT_KEY=$(python3 -c "import json; print(json.load(open('$HOME/.schift/memory/config/auth.json'))['api_key'])")
|
|
42
|
+
SCHIFT_API=$(python3 -c "import json; print(json.load(open('$HOME/.schift/memory/config/auth.json')).get('cloud_url','https://api.schift.io'))")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 2: Fetch and analyze the URL
|
|
46
|
+
|
|
47
|
+
Use WebFetch to get the content. Then produce a knowledge document with this structure:
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
---
|
|
51
|
+
title: <page title>
|
|
52
|
+
url: <original url>
|
|
53
|
+
fetched_at: <ISO 8601>
|
|
54
|
+
context: <why the user shared this / what they were working on>
|
|
55
|
+
domain: <domain tag>
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Summary
|
|
59
|
+
<2-3 sentence summary of what this page is about>
|
|
60
|
+
|
|
61
|
+
## Why this was investigated
|
|
62
|
+
<conversation context - what the user was doing, what question led here>
|
|
63
|
+
|
|
64
|
+
## Key findings
|
|
65
|
+
- <bullet points of the most important information>
|
|
66
|
+
- <actionable insights>
|
|
67
|
+
- <relevant data points>
|
|
68
|
+
|
|
69
|
+
## Original content
|
|
70
|
+
<extracted markdown from the page>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 3: Save locally
|
|
74
|
+
|
|
75
|
+
Write the knowledge document to the local replica:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Save to ~/.schift/memory/sources/web/<filename>.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use a descriptive filename: `<domain>_<slug>_<hash>.md`
|
|
82
|
+
|
|
83
|
+
### Step 4: Send to Schift Cloud
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
curl -s -X POST "${SCHIFT_API}/v1/memory/ingest-url" \
|
|
87
|
+
-H "Content-Type: application/json" \
|
|
88
|
+
-H "Authorization: Bearer ${SCHIFT_KEY}" \
|
|
89
|
+
-d '{
|
|
90
|
+
"url": "<the-url>",
|
|
91
|
+
"domain": "<domain>",
|
|
92
|
+
"title": "<title>",
|
|
93
|
+
"summary": "<one-line-summary>",
|
|
94
|
+
"context": "<why this was investigated>",
|
|
95
|
+
"findings": "<key findings as text>"
|
|
96
|
+
}'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Domain values
|
|
100
|
+
|
|
101
|
+
- `company` - company info, org structure
|
|
102
|
+
- `business` - strategy, market, positioning
|
|
103
|
+
- `finance` - accounting, pricing, revenue
|
|
104
|
+
- `decision` - past decisions, rationale
|
|
105
|
+
- `product` - product specs, features, roadmap
|
|
106
|
+
- `ops` - operations, processes, tools
|
|
107
|
+
- `research` - articles, papers, benchmarks
|
|
108
|
+
- `reference` - docs, tutorials, API references
|
|
109
|
+
|
|
110
|
+
## How to save a note
|
|
111
|
+
|
|
112
|
+
Same principle - include context:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl -s -X POST "${SCHIFT_API}/v1/memory/compact" \
|
|
116
|
+
-H "Content-Type: application/json" \
|
|
117
|
+
-H "Authorization: Bearer ${SCHIFT_KEY}" \
|
|
118
|
+
-d '{
|
|
119
|
+
"session_id": "<unique-id>",
|
|
120
|
+
"summary": "<the content + context>",
|
|
121
|
+
"domain": "<domain>",
|
|
122
|
+
"topic": "<optional-topic-slug>"
|
|
123
|
+
}'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## After saving
|
|
127
|
+
|
|
128
|
+
Confirm briefly:
|
|
129
|
+
- What was saved and key findings
|
|
130
|
+
- Which domain it was filed under
|
|
131
|
+
- "Saved locally + synced to Schift Cloud"
|
|
132
|
+
|
|
133
|
+
## Important
|
|
134
|
+
|
|
135
|
+
- NEVER save without the user's awareness. Ask first: "Save this to your knowledge base?"
|
|
136
|
+
- ALWAYS include conversation context (why it was looked up). A URL without context is just a bookmark.
|
|
137
|
+
- For sensitive content, warn before saving.
|
|
138
|
+
- If auth is missing, ALWAYS redirect to signup. No exceptions.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-search
|
|
3
|
+
description: Search Schift knowledge bucket for past conversations, saved URLs, notes, and documents. Use when user asks "what did we discuss about X", "find that article", or needs context from previous sessions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schift Memory - Search
|
|
7
|
+
|
|
8
|
+
Search your second brain for previously saved content.
|
|
9
|
+
|
|
10
|
+
## Auth check
|
|
11
|
+
|
|
12
|
+
Before ANY search operation, verify auth exists:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cat ~/.schift/memory/config/auth.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If the file is missing or has no `api_key`, STOP and tell the user:
|
|
19
|
+
|
|
20
|
+
> Schift Memory requires a free account.
|
|
21
|
+
> Run: `npx @schift-io/memory login`
|
|
22
|
+
> Or sign up: https://schift.io/signup?ref=memory-plugin
|
|
23
|
+
|
|
24
|
+
Do NOT proceed without a valid API key. There is no local-only mode.
|
|
25
|
+
|
|
26
|
+
## When to activate
|
|
27
|
+
|
|
28
|
+
- User asks "what did we talk about...", "find that article about...", "what was the decision on..."
|
|
29
|
+
- User needs context from a previous session
|
|
30
|
+
- Before starting work that might benefit from past knowledge
|
|
31
|
+
|
|
32
|
+
## How to search
|
|
33
|
+
|
|
34
|
+
Read auth first:
|
|
35
|
+
```bash
|
|
36
|
+
SCHIFT_KEY=$(python3 -c "import json; print(json.load(open('$HOME/.schift/memory/config/auth.json'))['api_key'])")
|
|
37
|
+
SCHIFT_API=$(python3 -c "import json; print(json.load(open('$HOME/.schift/memory/config/auth.json')).get('cloud_url','https://api.schift.io'))")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Query (recommended)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s -X POST "${SCHIFT_API}/v1/query" \
|
|
44
|
+
-H "Content-Type: application/json" \
|
|
45
|
+
-H "Authorization: Bearer ${SCHIFT_KEY}" \
|
|
46
|
+
-d '{
|
|
47
|
+
"query": "<natural language query>",
|
|
48
|
+
"collection": "localbucket",
|
|
49
|
+
"top_k": 5
|
|
50
|
+
}'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With domain filter (bucket search)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
curl -s -X POST "${SCHIFT_API}/v1/buckets/<bucket_id>/search" \
|
|
57
|
+
-H "Content-Type: application/json" \
|
|
58
|
+
-H "Authorization: Bearer ${SCHIFT_KEY}" \
|
|
59
|
+
-d '{
|
|
60
|
+
"query": "<query>",
|
|
61
|
+
"top_k": 5,
|
|
62
|
+
"filter": {"domain": "business"}
|
|
63
|
+
}'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Presenting results
|
|
67
|
+
|
|
68
|
+
- Show the top 3 results with title, domain, and a snippet
|
|
69
|
+
- If the result has `source_url`, include it
|
|
70
|
+
- If `source_type` is `session_compact`, note it came from a past conversation
|
|
71
|
+
- If `source_type` is `topic_synthesis`, it's an aggregated topic summary (highest quality)
|
|
72
|
+
|
|
73
|
+
## Source type priority
|
|
74
|
+
|
|
75
|
+
1. `topic_synthesis` - aggregated, most reliable
|
|
76
|
+
2. `session_compact` - conversation summaries
|
|
77
|
+
3. `raw` - original documents/web pages
|
|
78
|
+
|
|
79
|
+
## If no results
|
|
80
|
+
|
|
81
|
+
- Suggest broadening the query
|
|
82
|
+
- Note: "Your knowledge base might not have this yet. Want me to search the web and save it?"
|
|
83
|
+
- If auth error: redirect to `npx @schift-io/memory login`
|