@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 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)
@@ -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
@@ -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 ""
@@ -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`