@smart-coder-labs/nexusmind-mcp 0.2.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.
@@ -0,0 +1,25 @@
1
+ export interface Memory {
2
+ id: string;
3
+ user_id: string;
4
+ project: string;
5
+ tool: string;
6
+ content: string;
7
+ tags: string[];
8
+ created_at: string;
9
+ }
10
+ export interface StoreMemoryInput {
11
+ content: string;
12
+ project?: string;
13
+ tool?: string;
14
+ tags?: string[];
15
+ }
16
+ export interface StoreMemoryResponse {
17
+ id: string;
18
+ }
19
+ export declare function storeMemory(input: StoreMemoryInput): Promise<StoreMemoryResponse>;
20
+ export declare function searchMemories(query: string, limit?: number): Promise<Memory[]>;
21
+ export declare function listMemories(params?: {
22
+ project?: string;
23
+ tool?: string;
24
+ limit?: number;
25
+ }): Promise<Memory[]>;
package/dist/client.js ADDED
@@ -0,0 +1,67 @@
1
+ const BASE_URL = process.env.NEXUSMIND_BASE_URL ?? 'http://localhost:8080';
2
+ const API_KEY = process.env.NEXUSMIND_API_KEY ?? '';
3
+ if (!API_KEY) {
4
+ process.stderr.write('[nexusmind-mcp] Warning: NEXUSMIND_API_KEY is not set. ' +
5
+ 'Set it to your NexusMind API key.\n');
6
+ }
7
+ async function request(path, init) {
8
+ let res;
9
+ try {
10
+ res = await fetch(`${BASE_URL}${path}`, {
11
+ ...init,
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ Authorization: `Bearer ${API_KEY}`,
15
+ ...init?.headers,
16
+ },
17
+ });
18
+ }
19
+ catch {
20
+ const err = new Error(`NexusMind backend not reachable at ${BASE_URL}. Is it running?`);
21
+ err.status = 0;
22
+ throw err;
23
+ }
24
+ if (res.status === 401) {
25
+ const err = new Error('Invalid API key. Set NEXUSMIND_API_KEY to your NexusMind key.');
26
+ err.status = 401;
27
+ throw err;
28
+ }
29
+ if (!res.ok) {
30
+ const body = await res.json().catch(() => ({ error: res.statusText }));
31
+ const err = new Error(body.error ?? res.statusText);
32
+ err.status = res.status;
33
+ throw err;
34
+ }
35
+ if (res.status === 204)
36
+ return undefined;
37
+ return res.json();
38
+ }
39
+ // ── API calls ────────────────────────────────────────────────────────────────
40
+ export function storeMemory(input) {
41
+ return request('/v1/memory/store', {
42
+ method: 'POST',
43
+ body: JSON.stringify({
44
+ content: input.content,
45
+ project: input.project ?? '',
46
+ tool: input.tool ?? 'claude-code',
47
+ tags: input.tags ?? [],
48
+ }),
49
+ });
50
+ }
51
+ export function searchMemories(query, limit = 10) {
52
+ return request('/v1/memory/search', {
53
+ method: 'POST',
54
+ body: JSON.stringify({ query, limit }),
55
+ });
56
+ }
57
+ export function listMemories(params = {}) {
58
+ const qs = new URLSearchParams();
59
+ if (params.project)
60
+ qs.set('project', params.project);
61
+ if (params.tool)
62
+ qs.set('tool', params.tool);
63
+ if (params.limit)
64
+ qs.set('limit', String(params.limit));
65
+ return request(`/v1/memory?${qs}`);
66
+ }
67
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { storeMemory, searchMemories, listMemories } from './client.js';
6
+ // ── Helpers ──────────────────────────────────────────────────────────────────
7
+ function formatMemory(m) {
8
+ const date = new Date(m.created_at).toLocaleDateString();
9
+ const tags = m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
10
+ return `• [${m.tool}] ${m.project || '(no project)'} — ${m.content}${tags} (${date})`;
11
+ }
12
+ function formatList(memories) {
13
+ if (memories.length === 0)
14
+ return 'No memories found.';
15
+ return memories.map(formatMemory).join('\n');
16
+ }
17
+ // ── Server ───────────────────────────────────────────────────────────────────
18
+ const server = new McpServer({
19
+ name: 'nexusmind',
20
+ version: '0.1.0',
21
+ });
22
+ // store_memory
23
+ server.tool('store_memory', 'Store a memory, decision, or piece of context for later retrieval by the team.', {
24
+ content: z.string().describe('The memory content to store (decision, convention, finding, etc.)'),
25
+ project: z.string().optional().describe('Project or repo name (e.g. "nexusmind", "payments-api")'),
26
+ tool: z.string().optional().describe('Tool name — defaults to "claude-code"'),
27
+ tags: z.array(z.string()).optional().describe('Tags for categorization (e.g. ["auth", "convention"])'),
28
+ }, async ({ content, project, tool, tags }) => {
29
+ try {
30
+ const res = await storeMemory({ content, project, tool, tags });
31
+ return {
32
+ content: [{ type: 'text', text: `Memory stored (id: ${res.id})` }],
33
+ };
34
+ }
35
+ catch (err) {
36
+ return {
37
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
38
+ isError: true,
39
+ };
40
+ }
41
+ });
42
+ // search_memory
43
+ server.tool('search_memory', 'Search past memories and decisions stored by the team using full-text search.', {
44
+ query: z.string().describe('What to search for (e.g. "authentication", "database connection pool")'),
45
+ limit: z.number().int().min(1).max(50).optional().describe('Max results to return (default: 10)'),
46
+ }, async ({ query, limit }) => {
47
+ try {
48
+ const memories = await searchMemories(query, limit ?? 10);
49
+ const text = memories.length === 0
50
+ ? `No memories found for query: "${query}"`
51
+ : `Found ${memories.length} result(s) for "${query}":\n\n${formatList(memories)}`;
52
+ return { content: [{ type: 'text', text }] };
53
+ }
54
+ catch (err) {
55
+ return {
56
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
57
+ isError: true,
58
+ };
59
+ }
60
+ });
61
+ // list_memories
62
+ server.tool('list_memories', 'List recent memories stored by the team, optionally filtered by project or tool.', {
63
+ project: z.string().optional().describe('Filter by project name'),
64
+ tool: z.string().optional().describe('Filter by tool (e.g. "claude-code", "cursor")'),
65
+ limit: z.number().int().min(1).max(100).optional().describe('Max results (default: 20)'),
66
+ }, async ({ project, tool, limit }) => {
67
+ try {
68
+ const memories = await listMemories({ project, tool, limit: limit ?? 20 });
69
+ const text = memories.length === 0
70
+ ? 'No memories found.'
71
+ : `${memories.length} recent memory(ies):\n\n${formatList(memories)}`;
72
+ return { content: [{ type: 'text', text }] };
73
+ }
74
+ catch (err) {
75
+ return {
76
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
77
+ isError: true,
78
+ };
79
+ }
80
+ });
81
+ // ── Start ────────────────────────────────────────────────────────────────────
82
+ const transport = new StdioServerTransport();
83
+ await server.connect(transport);
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/setup.js ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import * as readline from 'node:readline/promises';
7
+ import { stdin as input, stdout as output } from 'node:process';
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const PLUGIN_DIR = join(__dirname, '..', 'plugin');
10
+ const CLAUDE_DIR = join(homedir(), '.claude');
11
+ const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');
12
+ const DEFAULT_BASE_URL = 'https://nexusmind-backend.fly.dev';
13
+ // ── Helpers ───────────────────────────────────────────────────────────────────
14
+ const c = {
15
+ reset: '\x1b[0m',
16
+ bold: '\x1b[1m',
17
+ green: '\x1b[32m',
18
+ blue: '\x1b[34m',
19
+ yellow: '\x1b[33m',
20
+ red: '\x1b[31m',
21
+ };
22
+ const info = (msg) => console.log(`${c.blue}[nexusmind]${c.reset} ${msg}`);
23
+ const success = (msg) => console.log(`${c.green}[nexusmind]${c.reset} ${msg}`);
24
+ const warn = (msg) => console.log(`${c.yellow}[nexusmind] WARNING:${c.reset} ${msg}`);
25
+ const err = (msg) => console.error(`${c.red}[nexusmind] ERROR:${c.reset} ${msg}`);
26
+ function readSettings() {
27
+ if (!existsSync(SETTINGS_PATH))
28
+ return {};
29
+ try {
30
+ return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ function writeSettings(settings) {
37
+ mkdirSync(CLAUDE_DIR, { recursive: true });
38
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
39
+ }
40
+ // ── Main ──────────────────────────────────────────────────────────────────────
41
+ console.log(`\n${c.bold}NexusMind — Claude Code Setup${c.reset}`);
42
+ console.log('────────────────────────────────\n');
43
+ // 1. Collect config
44
+ const rl = readline.createInterface({ input, output });
45
+ const apiKey = process.env.NEXUSMIND_API_KEY
46
+ ?? await rl.question('NexusMind API key: ');
47
+ const baseUrl = process.env.NEXUSMIND_BASE_URL
48
+ ?? await rl.question(`Backend URL [${DEFAULT_BASE_URL}]: `).then(v => v.trim() || DEFAULT_BASE_URL);
49
+ rl.close();
50
+ if (!apiKey.trim()) {
51
+ warn('No API key provided — you can set NEXUSMIND_API_KEY later and re-run setup.');
52
+ }
53
+ // 2. Read current settings
54
+ const settings = readSettings();
55
+ // 3. Merge MCP server
56
+ const mcpServers = settings.mcpServers ?? {};
57
+ mcpServers['nexusmind'] = {
58
+ command: 'npx',
59
+ args: ['-y', '@smart-coder-labs/nexusmind-mcp'],
60
+ env: {
61
+ NEXUSMIND_API_KEY: '${NEXUSMIND_API_KEY}',
62
+ NEXUSMIND_BASE_URL: baseUrl,
63
+ },
64
+ };
65
+ settings.mcpServers = mcpServers;
66
+ info('MCP server entry added.');
67
+ // 4. Merge hooks
68
+ const hooksPath = join(PLUGIN_DIR, 'hooks', 'hooks.json');
69
+ if (existsSync(hooksPath)) {
70
+ const pluginHooks = JSON.parse(readFileSync(hooksPath, 'utf8'));
71
+ const existing = settings.hooks ?? {};
72
+ for (const [event, entries] of Object.entries(pluginHooks.hooks)) {
73
+ if (!existing[event])
74
+ existing[event] = [];
75
+ for (const entry of entries) {
76
+ // Resolve ${CLAUDE_PLUGIN_ROOT} to actual plugin dir
77
+ const resolved = JSON.parse(JSON.stringify(entry).replaceAll('${CLAUDE_PLUGIN_ROOT}', PLUGIN_DIR));
78
+ // Dedup by command
79
+ const existingCmds = existing[event]
80
+ .flatMap(e => [e.command, ...(e.hooks ?? []).map(h => h.command)])
81
+ .filter(Boolean);
82
+ const newCmd = resolved.command ?? resolved.hooks?.[0]?.command ?? '';
83
+ if (!existingCmds.includes(newCmd)) {
84
+ existing[event].push(resolved);
85
+ }
86
+ }
87
+ }
88
+ settings.hooks = existing;
89
+ info('Hooks merged.');
90
+ }
91
+ else {
92
+ warn(`Hooks not found at ${hooksPath} — skipping hooks installation.`);
93
+ }
94
+ // 5. Write settings
95
+ writeSettings(settings);
96
+ success(`Settings written to ${SETTINGS_PATH}`);
97
+ // 6. Persist env vars to shell rc files
98
+ function appendEnvVar(rcFile, name, value) {
99
+ if (!existsSync(rcFile))
100
+ return;
101
+ const content = readFileSync(rcFile, 'utf8');
102
+ if (content.includes(`export ${name}=`)) {
103
+ warn(`${name} already in ${rcFile} — skipping.`);
104
+ return;
105
+ }
106
+ writeFileSync(rcFile, content + `\n# NexusMind\nexport ${name}="${value}"\n`);
107
+ success(`Wrote ${name} to ${rcFile}`);
108
+ }
109
+ if (apiKey.trim()) {
110
+ appendEnvVar(join(homedir(), '.zshrc'), 'NEXUSMIND_API_KEY', apiKey.trim());
111
+ appendEnvVar(join(homedir(), '.bashrc'), 'NEXUSMIND_API_KEY', apiKey.trim());
112
+ }
113
+ appendEnvVar(join(homedir(), '.zshrc'), 'NEXUSMIND_BASE_URL', baseUrl);
114
+ appendEnvVar(join(homedir(), '.bashrc'), 'NEXUSMIND_BASE_URL', baseUrl);
115
+ // ── Done ──────────────────────────────────────────────────────────────────────
116
+ console.log(`\n${c.bold}${c.green}Done!${c.reset}\n`);
117
+ console.log('Next steps:');
118
+ console.log(' 1. Restart your shell or run: source ~/.zshrc');
119
+ console.log(' 2. Open Claude Code — NexusMind connects automatically');
120
+ console.log(' 3. store_memory, search_memory, list_memories are now available\n');
121
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@smart-coder-labs/nexusmind-mcp",
3
+ "version": "0.2.0",
4
+ "description": "NexusMind MCP server for Claude Code — team memory that persists across sessions",
5
+ "type": "module",
6
+ "bin": {
7
+ "nexusmind-mcp": "dist/index.js",
8
+ "nexusmind-setup": "dist/setup.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "setup": "node dist/setup.js"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "plugin"
19
+ ],
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.29.0",
22
+ "zod": "^3.22.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^20.0.0",
26
+ "tsx": "^4.6.0",
27
+ "typescript": "^5.3.0"
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "nexusmind",
3
+ "description": "Team memory for AI coding agents. Store decisions, bugs, and conventions — shared across the whole team.",
4
+ "version": "0.1.0",
5
+ "author": { "name": "NexusMind" },
6
+ "homepage": "https://nexusmind.smartcoderlabs.com",
7
+ "repository": "https://github.com/smart-coder-labs/nexus-mind",
8
+ "license": "MIT"
9
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "nexusmind": {
4
+ "command": "npx",
5
+ "args": ["-y", "@smart-coder-labs/nexusmind-mcp"],
6
+ "env": {
7
+ "NEXUSMIND_API_KEY": "${NEXUSMIND_API_KEY}",
8
+ "NEXUSMIND_BASE_URL": "${NEXUSMIND_BASE_URL:-https://nexusmind-backend.fly.dev}"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,71 @@
1
+ # NexusMind — Claude Code Plugin
2
+
3
+ Team memory for AI coding agents. Store decisions, bugs, and conventions — shared across the whole team.
4
+
5
+ ## What it does
6
+
7
+ - Injects your team's recent memories into every Claude Code session as context
8
+ - Provides `store_memory`, `search_memory`, and `list_memories` tools via MCP
9
+ - Reminds Claude Code to save decisions proactively throughout the session
10
+ - Passively captures subagent output for team context
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 18+
15
+ - A NexusMind API key (`NEXUSMIND_API_KEY`)
16
+ - Claude Code
17
+
18
+ ## Install
19
+
20
+ ### Option 1 — one-liner
21
+
22
+ ```bash
23
+ curl -fsSL https://raw.githubusercontent.com/smart-coder-labs/nexus-mind/main/plugin/claude-code/install.sh | bash
24
+ ```
25
+
26
+ ### Option 2 — clone and run
27
+
28
+ ```bash
29
+ git clone https://github.com/smart-coder-labs/nexus-mind.git
30
+ bash nexus-mind/plugin/claude-code/install.sh
31
+ ```
32
+
33
+ ### Option 3 — Claude plugin marketplace
34
+
35
+ ```bash
36
+ claude plugin marketplace add smart-coder-labs/nexus-mind
37
+ claude plugin install nexusmind
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ | Variable | Default | Description |
43
+ |----------|---------|-------------|
44
+ | `NEXUSMIND_API_KEY` | — | Your NexusMind API key (required) |
45
+ | `NEXUSMIND_BASE_URL` | `https://nexusmind-backend.fly.dev` | Backend URL (optional, for self-hosting) |
46
+
47
+ Set these in your shell profile or pass them before running Claude Code:
48
+
49
+ ```bash
50
+ export NEXUSMIND_API_KEY=your-key-here
51
+ ```
52
+
53
+ ## What gets installed
54
+
55
+ The installer:
56
+ 1. Adds the `nexusmind` MCP server to `~/.claude/settings.json`
57
+ 2. Adds lifecycle hooks (SessionStart, UserPromptSubmit, SubagentStop, Stop)
58
+ 3. Writes `NEXUSMIND_API_KEY` and `NEXUSMIND_BASE_URL` to `~/.bashrc` and `~/.zshrc`
59
+
60
+ ## MCP Tools
61
+
62
+ | Tool | When to use |
63
+ |------|-------------|
64
+ | `store_memory` | Save a decision, bug fix, convention, or discovery |
65
+ | `search_memory` | Look up past decisions or context |
66
+ | `list_memories` | Browse recent team memories |
67
+
68
+ ## Uninstall
69
+
70
+ Remove the `nexusmind` entry from `~/.claude/settings.json` under both `mcpServers` and `hooks`.
71
+ Remove the `NEXUSMIND_API_KEY` and `NEXUSMIND_BASE_URL` exports from your shell profile.
@@ -0,0 +1,59 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|clear",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-start.sh"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "matcher": "compact",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/post-compaction.sh"
19
+ }
20
+ ]
21
+ }
22
+ ],
23
+ "UserPromptSubmit": [
24
+ {
25
+ "matcher": "",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-submit.sh"
30
+ }
31
+ ]
32
+ }
33
+ ],
34
+ "SubagentStop": [
35
+ {
36
+ "matcher": "",
37
+ "hooks": [
38
+ {
39
+ "type": "command",
40
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/subagent-stop.sh",
41
+ "async": true
42
+ }
43
+ ]
44
+ }
45
+ ],
46
+ "Stop": [
47
+ {
48
+ "matcher": "",
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-stop.sh",
53
+ "async": true
54
+ }
55
+ ]
56
+ }
57
+ ]
58
+ }
59
+ }
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — NexusMind Claude Code plugin installer
3
+ # Sets up the MCP server entry and hooks in ~/.claude/settings.json
4
+ # and writes environment variables to shell rc files.
5
+ set -euo pipefail
6
+
7
+ PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ CLAUDE_SETTINGS="${HOME}/.claude/settings.json"
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Colors
12
+ # ---------------------------------------------------------------------------
13
+ RED='\033[0;31m'
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ BOLD='\033[1m'
18
+ RESET='\033[0m'
19
+
20
+ info() { echo -e "${BLUE}[nexusmind]${RESET} $*"; }
21
+ success() { echo -e "${GREEN}[nexusmind]${RESET} $*"; }
22
+ warn() { echo -e "${YELLOW}[nexusmind] WARNING:${RESET} $*"; }
23
+ error() { echo -e "${RED}[nexusmind] ERROR:${RESET} $*" >&2; }
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Dependency checks (warn, don't fail)
27
+ # ---------------------------------------------------------------------------
28
+ echo ""
29
+ echo -e "${BOLD}NexusMind — Claude Code Plugin Installer${RESET}"
30
+ echo "────────────────────────────────────────"
31
+ echo ""
32
+
33
+ if ! command -v node &>/dev/null; then
34
+ warn "node is not installed. The MCP server requires Node.js 18+."
35
+ warn "Install it from https://nodejs.org before using NexusMind."
36
+ fi
37
+
38
+ if ! command -v jq &>/dev/null; then
39
+ warn "jq is not installed. JSON merging will use python3 as fallback."
40
+ fi
41
+
42
+ if ! command -v python3 &>/dev/null; then
43
+ error "python3 is required for JSON merging but was not found."
44
+ error "Install python3 and re-run this installer."
45
+ exit 1
46
+ fi
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Collect configuration
50
+ # ---------------------------------------------------------------------------
51
+
52
+ # API key
53
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
54
+ echo -n "Enter your NexusMind API key: "
55
+ read -r NEXUSMIND_API_KEY
56
+ if [[ -z "$NEXUSMIND_API_KEY" ]]; then
57
+ warn "No API key provided. You can set it later with: export NEXUSMIND_API_KEY=<your-key>"
58
+ NEXUSMIND_API_KEY=""
59
+ fi
60
+ fi
61
+
62
+ # Base URL
63
+ DEFAULT_URL="https://nexusmind-backend.fly.dev"
64
+ if [[ -z "${NEXUSMIND_BASE_URL:-}" ]]; then
65
+ echo -n "NexusMind backend URL [${DEFAULT_URL}]: "
66
+ read -r NEXUSMIND_BASE_URL
67
+ NEXUSMIND_BASE_URL="${NEXUSMIND_BASE_URL:-$DEFAULT_URL}"
68
+ fi
69
+
70
+ info "Using backend: ${NEXUSMIND_BASE_URL}"
71
+ echo ""
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Ensure ~/.claude directory and settings.json exist
75
+ # ---------------------------------------------------------------------------
76
+ mkdir -p "${HOME}/.claude"
77
+
78
+ if [[ ! -f "$CLAUDE_SETTINGS" ]]; then
79
+ echo '{}' > "$CLAUDE_SETTINGS"
80
+ info "Created ${CLAUDE_SETTINGS}"
81
+ fi
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Helper: merge JSON via python3
85
+ # ---------------------------------------------------------------------------
86
+ merge_json() {
87
+ # merge_json <file> <python_snippet_that_modifies_variable_d>
88
+ local file="$1"
89
+ local snippet="$2"
90
+ python3 -c "
91
+ import json, sys
92
+
93
+ with open('${file}', 'r') as f:
94
+ d = json.load(f)
95
+
96
+ ${snippet}
97
+
98
+ with open('${file}', 'w') as f:
99
+ json.dump(d, f, indent=2)
100
+ f.write('\n')
101
+ "
102
+ }
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # 1. Write MCP server entry
106
+ # ---------------------------------------------------------------------------
107
+ info "Adding MCP server entry to ${CLAUDE_SETTINGS}..."
108
+
109
+ merge_json "$CLAUDE_SETTINGS" "
110
+ if 'mcpServers' not in d:
111
+ d['mcpServers'] = {}
112
+ d['mcpServers']['nexusmind'] = {
113
+ 'command': 'npx',
114
+ 'args': ['-y', '@smart-coder-labs/nexusmind-mcp'],
115
+ 'env': {
116
+ 'NEXUSMIND_API_KEY': '\${NEXUSMIND_API_KEY}',
117
+ 'NEXUSMIND_BASE_URL': '\${NEXUSMIND_BASE_URL:-${NEXUSMIND_BASE_URL}}'
118
+ }
119
+ }
120
+ "
121
+ success "MCP server entry added."
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # 2. Read hooks from hooks.json and merge into settings.json
125
+ # ---------------------------------------------------------------------------
126
+ HOOKS_JSON="${PLUGIN_DIR}/hooks/hooks.json"
127
+
128
+ if [[ ! -f "$HOOKS_JSON" ]]; then
129
+ warn "hooks.json not found at ${HOOKS_JSON}. Skipping hooks installation."
130
+ else
131
+ info "Merging hooks into ${CLAUDE_SETTINGS}..."
132
+
133
+ PLUGIN_ROOT_ESCAPED="$(echo "${PLUGIN_DIR}/scripts" | sed 's/[\/&]/\\&/g')"
134
+
135
+ python3 -c "
136
+ import json
137
+
138
+ with open('${CLAUDE_SETTINGS}', 'r') as f:
139
+ settings = json.load(f)
140
+
141
+ with open('${HOOKS_JSON}', 'r') as f:
142
+ hooks_data = json.load(f)
143
+
144
+ plugin_root = '${PLUGIN_DIR}'
145
+
146
+ if 'hooks' not in settings:
147
+ settings['hooks'] = {}
148
+
149
+ # Merge each hook event
150
+ for event, entries in hooks_data.get('hooks', {}).items():
151
+ if event not in settings['hooks']:
152
+ settings['hooks'][event] = []
153
+ for entry in entries:
154
+ # Replace \${CLAUDE_PLUGIN_ROOT}/scripts with actual path
155
+ resolved_entry = json.loads(
156
+ json.dumps(entry).replace('\${CLAUDE_PLUGIN_ROOT}', plugin_root)
157
+ )
158
+ # Avoid duplicates based on command
159
+ existing_commands = [
160
+ h.get('command', '')
161
+ for group in settings['hooks'][event]
162
+ for h in (group.get('hooks', []) if isinstance(group, dict) and 'hooks' in group else [group])
163
+ ]
164
+ new_command = resolved_entry.get('command', '') or (
165
+ resolved_entry.get('hooks', [{}])[0].get('command', '')
166
+ if isinstance(resolved_entry, dict) and 'hooks' in resolved_entry else ''
167
+ )
168
+ if new_command not in existing_commands:
169
+ settings['hooks'][event].append(resolved_entry)
170
+
171
+ with open('${CLAUDE_SETTINGS}', 'w') as f:
172
+ json.dump(settings, f, indent=2)
173
+ f.write('\n')
174
+ "
175
+ success "Hooks merged."
176
+ fi
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # 3. Write env vars to shell rc files
180
+ # ---------------------------------------------------------------------------
181
+ write_env_var() {
182
+ local rc_file="$1"
183
+ local var_name="$2"
184
+ local var_value="$3"
185
+
186
+ if [[ ! -f "$rc_file" ]]; then
187
+ return 0
188
+ fi
189
+
190
+ # Skip if already set in the file
191
+ if grep -q "export ${var_name}=" "$rc_file" 2>/dev/null; then
192
+ warn "${var_name} already set in ${rc_file} — skipping."
193
+ return 0
194
+ fi
195
+
196
+ echo "" >> "$rc_file"
197
+ echo "# NexusMind" >> "$rc_file"
198
+ echo "export ${var_name}=\"${var_value}\"" >> "$rc_file"
199
+ success "Wrote ${var_name} to ${rc_file}"
200
+ }
201
+
202
+ if [[ -n "$NEXUSMIND_API_KEY" ]]; then
203
+ write_env_var "${HOME}/.bashrc" "NEXUSMIND_API_KEY" "$NEXUSMIND_API_KEY"
204
+ write_env_var "${HOME}/.zshrc" "NEXUSMIND_API_KEY" "$NEXUSMIND_API_KEY"
205
+ fi
206
+
207
+ write_env_var "${HOME}/.bashrc" "NEXUSMIND_BASE_URL" "$NEXUSMIND_BASE_URL"
208
+ write_env_var "${HOME}/.zshrc" "NEXUSMIND_BASE_URL" "$NEXUSMIND_BASE_URL"
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Done
212
+ # ---------------------------------------------------------------------------
213
+ echo ""
214
+ echo -e "${BOLD}${GREEN}Installation complete!${RESET}"
215
+ echo ""
216
+ echo "Next steps:"
217
+ echo " 1. Restart your shell or run: source ~/.zshrc (or ~/.bashrc)"
218
+ echo " 2. Open Claude Code — NexusMind will connect automatically"
219
+ echo " 3. The MCP tools (store_memory, search_memory, list_memories) are now available"
220
+ echo ""
221
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
222
+ echo -e "${YELLOW}Remember to set your API key:${RESET}"
223
+ echo " export NEXUSMIND_API_KEY=<your-key>"
224
+ echo ""
225
+ fi
226
+ echo "Documentation: https://nexusmind.smartcoderlabs.com"
227
+ echo ""
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ # _helpers.sh — shared helper functions for NexusMind Claude Code plugin scripts
3
+
4
+ # detect_project: determines the project name from git or directory context.
5
+ # Priority: git remote origin repo name → git root basename → cwd basename.
6
+ detect_project() {
7
+ local project=""
8
+
9
+ # 1. Try git remote origin URL → extract repo name
10
+ if git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
11
+ local remote_url
12
+ remote_url="$(git remote get-url origin 2>/dev/null || true)"
13
+ if [[ -n "$remote_url" ]]; then
14
+ # Strip trailing .git, then extract last path component
15
+ project="$(basename "$remote_url" .git)"
16
+ fi
17
+
18
+ # 2. Fallback: git root directory basename
19
+ if [[ -z "$project" ]]; then
20
+ project="$(basename "$(git rev-parse --show-toplevel 2>/dev/null || true)")"
21
+ fi
22
+ fi
23
+
24
+ # 3. Final fallback: current working directory basename
25
+ if [[ -z "$project" ]]; then
26
+ project="$(basename "$PWD")"
27
+ fi
28
+
29
+ echo "$project"
30
+ }
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env bash
2
+ # post-compaction.sh — NexusMind Claude Code plugin: SessionStart (compact matcher) hook
3
+ # Outputs additionalContext after a compaction event with recovery instructions.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=./_helpers.sh
8
+ source "${SCRIPT_DIR}/_helpers.sh"
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Parse stdin JSON
12
+ # ---------------------------------------------------------------------------
13
+ INPUT="$(cat)"
14
+
15
+ session_id="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('session_id',''))" 2>/dev/null || true)"
16
+ cwd="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('cwd',''))" 2>/dev/null || true)"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Config
20
+ # ---------------------------------------------------------------------------
21
+ NEXUSMIND_BASE_URL="${NEXUSMIND_BASE_URL:-https://nexusmind-backend.fly.dev}"
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Guard: API key required
25
+ # ---------------------------------------------------------------------------
26
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
27
+ cat <<'EOF'
28
+ <!-- NexusMind NOT CONNECTED after compaction: NEXUSMIND_API_KEY is not set. -->
29
+ EOF
30
+ exit 0
31
+ fi
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Guard: backend health check
35
+ # ---------------------------------------------------------------------------
36
+ if ! curl -sf --max-time 5 "${NEXUSMIND_BASE_URL}/v1/health" &>/dev/null; then
37
+ cat <<'EOF'
38
+ <!-- NexusMind NOT CONNECTED after compaction: backend is unreachable. -->
39
+ EOF
40
+ exit 0
41
+ fi
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Detect project
45
+ # ---------------------------------------------------------------------------
46
+ if [[ -n "$cwd" ]]; then
47
+ pushd "$cwd" &>/dev/null || true
48
+ fi
49
+
50
+ PROJECT="$(detect_project)"
51
+
52
+ if [[ -n "$cwd" ]]; then
53
+ popd &>/dev/null || true
54
+ fi
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Fetch recent memories
58
+ # ---------------------------------------------------------------------------
59
+ RECENT_MEMORIES=""
60
+ MEMORIES_RESPONSE="$(curl -sf --max-time 10 \
61
+ -H "Authorization: Bearer ${NEXUSMIND_API_KEY}" \
62
+ -H "Content-Type: application/json" \
63
+ "${NEXUSMIND_BASE_URL}/v1/memory?limit=15" 2>/dev/null || true)"
64
+
65
+ if [[ -n "$MEMORIES_RESPONSE" ]]; then
66
+ RECENT_MEMORIES="$(echo "$MEMORIES_RESPONSE" | python3 -c "
67
+ import sys, json
68
+ try:
69
+ data = json.load(sys.stdin)
70
+ items = data if isinstance(data, list) else data.get('memories', data.get('items', data.get('data', [])))
71
+ lines = []
72
+ for m in items[:10]:
73
+ project = m.get('project', '') or m.get('metadata', {}).get('project', '')
74
+ tool = m.get('tool', '') or m.get('metadata', {}).get('tool', '')
75
+ content = m.get('content', m.get('text', ''))
76
+ label = '/'.join(filter(None, [project, tool]))
77
+ snippet = content[:120].replace('\n', ' ')
78
+ lines.append(f'- [{label}] {snippet}')
79
+ print('\n'.join(lines))
80
+ except Exception:
81
+ pass
82
+ " 2>/dev/null || true)"
83
+ fi
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Output additionalContext with compaction-specific recovery instructions
87
+ # ---------------------------------------------------------------------------
88
+ cat <<PROTOCOL
89
+ ## NexusMind — ACTIVE PROTOCOL (post-compaction recovery)
90
+
91
+ Context was compacted. You MUST recover state before continuing.
92
+
93
+ **FIRST ACTION REQUIRED after compaction:**
94
+ 1. Call store_memory with a summary of what was being worked on before compaction
95
+ 2. Call search_memory to recover prior decisions and context
96
+ 3. Only then continue working
97
+
98
+ **Project detected**: ${PROJECT}
99
+
100
+ ### CORE TOOLS
101
+ store_memory — save decisions, bugs, discoveries, conventions PROACTIVELY (do not wait to be asked)
102
+ search_memory — find past decisions or context
103
+ list_memories — browse recent memories
104
+
105
+ ### PROACTIVE SAVE RULE
106
+ Call store_memory IMMEDIATELY after ANY decision, bug fix, discovery, or convention — not just when asked.
107
+ Always pass tool="claude-code" and project="${PROJECT}".
108
+
109
+ ### WHEN TO SEARCH
110
+ - User's first message references a feature or problem → search_memory with keywords
111
+ - Starting work on something that might have been done before → search_memory
112
+ - User asks to recall anything → search_memory
113
+
114
+ ### SESSION CLOSE (MANDATORY)
115
+ Before saying "done", call store_memory with:
116
+ - What was accomplished
117
+ - Key decisions and why
118
+ - Files changed
119
+ - Next steps
120
+
121
+ This is NOT optional. If you skip this, the next session starts blind.
122
+ PROTOCOL
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Append recent memories if available
126
+ # ---------------------------------------------------------------------------
127
+ if [[ -n "$RECENT_MEMORIES" ]]; then
128
+ cat <<MEMORIES
129
+
130
+ ### Recent Team Memories (last 10)
131
+ ${RECENT_MEMORIES}
132
+ MEMORIES
133
+ fi
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ # session-start.sh — NexusMind Claude Code plugin: SessionStart hook
3
+ # Outputs additionalContext for Claude Code with the NexusMind active protocol.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=./_helpers.sh
8
+ source "${SCRIPT_DIR}/_helpers.sh"
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Parse stdin JSON (Claude Code passes hook payload via stdin)
12
+ # ---------------------------------------------------------------------------
13
+ INPUT="$(cat)"
14
+
15
+ session_id="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('session_id',''))" 2>/dev/null || true)"
16
+ cwd="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('cwd',''))" 2>/dev/null || true)"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Config
20
+ # ---------------------------------------------------------------------------
21
+ NEXUSMIND_BASE_URL="${NEXUSMIND_BASE_URL:-https://nexusmind-backend.fly.dev}"
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Guard: API key required
25
+ # ---------------------------------------------------------------------------
26
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
27
+ cat <<'EOF'
28
+ <!-- NexusMind NOT CONNECTED: NEXUSMIND_API_KEY is not set. Memory tools will not be available.
29
+ Run: export NEXUSMIND_API_KEY=<your-key>
30
+ Then restart Claude Code. -->
31
+ EOF
32
+ exit 0
33
+ fi
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Guard: backend health check
37
+ # ---------------------------------------------------------------------------
38
+ if ! curl -sf --max-time 5 "${NEXUSMIND_BASE_URL}/v1/health" &>/dev/null; then
39
+ cat <<'EOF'
40
+ <!-- NexusMind NOT CONNECTED: backend is unreachable. Memory tools will not be available.
41
+ Check NEXUSMIND_BASE_URL or your network connection. -->
42
+ EOF
43
+ exit 0
44
+ fi
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Detect project
48
+ # ---------------------------------------------------------------------------
49
+ if [[ -n "$cwd" ]]; then
50
+ pushd "$cwd" &>/dev/null || true
51
+ fi
52
+
53
+ PROJECT="$(detect_project)"
54
+
55
+ if [[ -n "$cwd" ]]; then
56
+ popd &>/dev/null || true
57
+ fi
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Fetch recent memories
61
+ # ---------------------------------------------------------------------------
62
+ RECENT_MEMORIES=""
63
+ MEMORIES_RESPONSE="$(curl -sf --max-time 10 \
64
+ -H "Authorization: Bearer ${NEXUSMIND_API_KEY}" \
65
+ -H "Content-Type: application/json" \
66
+ "${NEXUSMIND_BASE_URL}/v1/memory?limit=15" 2>/dev/null || true)"
67
+
68
+ if [[ -n "$MEMORIES_RESPONSE" ]]; then
69
+ RECENT_MEMORIES="$(echo "$MEMORIES_RESPONSE" | python3 -c "
70
+ import sys, json
71
+ try:
72
+ data = json.load(sys.stdin)
73
+ items = data if isinstance(data, list) else data.get('memories', data.get('items', data.get('data', [])))
74
+ lines = []
75
+ for m in items[:10]:
76
+ project = m.get('project', '') or m.get('metadata', {}).get('project', '')
77
+ tool = m.get('tool', '') or m.get('metadata', {}).get('tool', '')
78
+ content = m.get('content', m.get('text', ''))
79
+ label = '/'.join(filter(None, [project, tool]))
80
+ snippet = content[:120].replace('\n', ' ')
81
+ lines.append(f'- [{label}] {snippet}')
82
+ print('\n'.join(lines))
83
+ except Exception:
84
+ pass
85
+ " 2>/dev/null || true)"
86
+ fi
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Output additionalContext
90
+ # ---------------------------------------------------------------------------
91
+ cat <<PROTOCOL
92
+ ## NexusMind — ACTIVE PROTOCOL
93
+
94
+ You have NexusMind memory tools available. This protocol is MANDATORY and ALWAYS ACTIVE for this project.
95
+
96
+ **Project detected**: ${PROJECT}
97
+
98
+ ### CORE TOOLS
99
+ store_memory — save decisions, bugs, discoveries, conventions PROACTIVELY (do not wait to be asked)
100
+ search_memory — find past decisions or context
101
+ list_memories — browse recent memories
102
+
103
+ ### PROACTIVE SAVE RULE
104
+ Call store_memory IMMEDIATELY after ANY decision, bug fix, discovery, or convention — not just when asked.
105
+ Always pass tool="claude-code" and project="${PROJECT}".
106
+
107
+ ### WHEN TO SEARCH
108
+ - User's first message references a feature or problem → search_memory with keywords
109
+ - Starting work on something that might have been done before → search_memory
110
+ - User asks to recall anything → search_memory
111
+
112
+ ### SESSION CLOSE (MANDATORY)
113
+ Before saying "done", call store_memory with:
114
+ - What was accomplished
115
+ - Key decisions and why
116
+ - Files changed
117
+ - Next steps
118
+
119
+ This is NOT optional. If you skip this, the next session starts blind.
120
+ PROTOCOL
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Append recent memories if available
124
+ # ---------------------------------------------------------------------------
125
+ if [[ -n "$RECENT_MEMORIES" ]]; then
126
+ cat <<MEMORIES
127
+
128
+ ### Recent Team Memories (last 10)
129
+ ${RECENT_MEMORIES}
130
+ MEMORIES
131
+ fi
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # session-stop.sh — NexusMind Claude Code plugin: Stop hook (async)
3
+ # NexusMind does not have server-side sessions, so this is a no-op.
4
+ set -euo pipefail
5
+
6
+ exit 0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # subagent-stop.sh — NexusMind Claude Code plugin: SubagentStop hook (async)
3
+ # Passively captures subagent output as a memory entry for team context.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=./_helpers.sh
8
+ source "${SCRIPT_DIR}/_helpers.sh"
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Guard: nothing to do without an API key
12
+ # ---------------------------------------------------------------------------
13
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
14
+ exit 0
15
+ fi
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Parse stdin JSON
19
+ # ---------------------------------------------------------------------------
20
+ INPUT="$(cat)"
21
+
22
+ subagent_output="$(echo "$INPUT" | python3 -c "
23
+ import sys, json
24
+ try:
25
+ d = json.load(sys.stdin)
26
+ print(d.get('stdout', ''))
27
+ except Exception:
28
+ pass
29
+ " 2>/dev/null || true)"
30
+
31
+ # Skip if output is empty or very short (not worth storing)
32
+ if [[ -z "$subagent_output" || "${#subagent_output}" -lt 50 ]]; then
33
+ exit 0
34
+ fi
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Config
38
+ # ---------------------------------------------------------------------------
39
+ NEXUSMIND_BASE_URL="${NEXUSMIND_BASE_URL:-https://nexusmind-backend.fly.dev}"
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Detect project
43
+ # ---------------------------------------------------------------------------
44
+ PROJECT="$(detect_project)"
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Fire-and-forget: POST to /v1/memory/store
48
+ # ---------------------------------------------------------------------------
49
+ PAYLOAD="$(python3 -c "
50
+ import json, sys
51
+ content = sys.argv[1]
52
+ project = sys.argv[2]
53
+ # Truncate to avoid oversized payloads
54
+ if len(content) > 2000:
55
+ content = content[:2000] + '... [truncated]'
56
+ print(json.dumps({
57
+ 'content': content,
58
+ 'tool': 'claude-code-subagent',
59
+ 'project': project,
60
+ 'metadata': {
61
+ 'source': 'subagent-stop-hook',
62
+ 'passive_capture': True
63
+ }
64
+ }))
65
+ " "$subagent_output" "$PROJECT" 2>/dev/null || true)"
66
+
67
+ if [[ -n "$PAYLOAD" ]]; then
68
+ curl -sf --max-time 10 \
69
+ -X POST \
70
+ -H "Authorization: Bearer ${NEXUSMIND_API_KEY}" \
71
+ -H "Content-Type: application/json" \
72
+ -d "$PAYLOAD" \
73
+ "${NEXUSMIND_BASE_URL}/v1/memory/store" &>/dev/null || true
74
+ fi
75
+
76
+ exit 0
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ # user-prompt-submit.sh — NexusMind Claude Code plugin: UserPromptSubmit hook
3
+ # Outputs a systemMessage to reinforce memory tool usage on first prompt and periodically.
4
+ set -euo pipefail
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Parse stdin JSON
8
+ # ---------------------------------------------------------------------------
9
+ INPUT="$(cat)"
10
+
11
+ session_id="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('session_id','unknown'))" 2>/dev/null || echo "unknown")"
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Guard: nothing to do without an API key
15
+ # ---------------------------------------------------------------------------
16
+ if [[ -z "${NEXUSMIND_API_KEY:-}" ]]; then
17
+ exit 0
18
+ fi
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # State file for this session
22
+ # ---------------------------------------------------------------------------
23
+ STATE_FILE="/tmp/nexusmind-session-${session_id}"
24
+ NOW="$(date +%s)"
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # First call for this session → output initial system message
28
+ # ---------------------------------------------------------------------------
29
+ if [[ ! -f "$STATE_FILE" ]]; then
30
+ # Create state file with: session_start_time last_reminder_time prompt_count
31
+ echo "${NOW} ${NOW} 1" > "$STATE_FILE"
32
+ cat <<'JSON'
33
+ {"systemMessage": "NexusMind memory tools are available. Use store_memory to save decisions, bugs, and discoveries proactively. Use search_memory to look up past context."}
34
+ JSON
35
+ exit 0
36
+ fi
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Read existing state
40
+ # ---------------------------------------------------------------------------
41
+ read -r SESSION_START LAST_REMINDER PROMPT_COUNT < "$STATE_FILE" 2>/dev/null || {
42
+ SESSION_START="$NOW"
43
+ LAST_REMINDER="$NOW"
44
+ PROMPT_COUNT=0
45
+ }
46
+
47
+ PROMPT_COUNT=$(( PROMPT_COUNT + 1 ))
48
+
49
+ SESSION_AGE=$(( NOW - SESSION_START )) # seconds since session start
50
+ TIME_SINCE_REMINDER=$(( NOW - LAST_REMINDER )) # seconds since last reminder
51
+
52
+ # Update state
53
+ echo "${SESSION_START} ${LAST_REMINDER} ${PROMPT_COUNT}" > "$STATE_FILE"
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Periodic reminder: 15+ min since last reminder AND session is 5+ min old
57
+ # ---------------------------------------------------------------------------
58
+ FIFTEEN_MIN=900
59
+ FIVE_MIN=300
60
+
61
+ if (( TIME_SINCE_REMINDER >= FIFTEEN_MIN && SESSION_AGE >= FIVE_MIN )); then
62
+ # Update last reminder timestamp
63
+ echo "${SESSION_START} ${NOW} ${PROMPT_COUNT}" > "$STATE_FILE"
64
+ cat <<'JSON'
65
+ {"systemMessage": "MEMORY REMINDER: Save recent decisions and discoveries to NexusMind with store_memory."}
66
+ JSON
67
+ exit 0
68
+ fi
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Nothing to output this time
72
+ # ---------------------------------------------------------------------------
73
+ exit 0
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: nexusmind-memory
3
+ description: NexusMind persistent team memory protocol — always active
4
+ triggers: always
5
+ ---
6
+
7
+ # NexusMind Memory — Always-Active Protocol
8
+
9
+ You have NexusMind memory tools available. This protocol is MANDATORY and ALWAYS ACTIVE for this project.
10
+
11
+ ## Core Tools
12
+
13
+ | Tool | Purpose |
14
+ |------|---------|
15
+ | `store_memory` | Save decisions, bugs, discoveries, conventions — PROACTIVELY, without being asked |
16
+ | `search_memory` | Find past decisions or context before starting any relevant work |
17
+ | `list_memories` | Browse recent team memories |
18
+
19
+ ## Proactive Save Rule
20
+
21
+ Call `store_memory` IMMEDIATELY after ANY of the following — do NOT wait to be asked:
22
+
23
+ - Architecture or design decision made
24
+ - Bug fixed (include root cause and what broke)
25
+ - Convention documented or established
26
+ - Tool or library choice made with reasoning
27
+ - Non-obvious discovery, gotcha, or edge case found
28
+ - Pattern established (naming, structure, approach)
29
+ - Feature implemented with a non-obvious approach
30
+ - Any configuration or environment change
31
+
32
+ Always pass `tool="claude-code"` and set `project` to the detected project name.
33
+
34
+ **Self-check after EVERY task**: "Did I make a decision, fix a bug, learn something non-obvious, or establish a convention? If yes, call store_memory NOW."
35
+
36
+ ## When to Search
37
+
38
+ - User's first message references a feature or problem → call `search_memory` with keywords from their message
39
+ - Starting work on something that might have been done before → call `search_memory`
40
+ - User asks to recall anything → call `search_memory`
41
+ - User mentions a topic you have no context on → call `search_memory`
42
+
43
+ ## Session Close (MANDATORY)
44
+
45
+ Before saying "done" (or any equivalent in the user's language), call `store_memory` with a session summary:
46
+
47
+ ```
48
+ What was accomplished this session
49
+ Key decisions made and why
50
+ Files changed (with paths)
51
+ Next steps for the following session
52
+ ```
53
+
54
+ This is NOT optional. Skipping this means the next session starts blind — no team context, no continuity.
55
+
56
+ ## After Compaction
57
+
58
+ If you see a compaction message or "FIRST ACTION REQUIRED":
59
+
60
+ 1. IMMEDIATELY call `store_memory` with a summary of what was being worked on before compaction
61
+ 2. Call `search_memory` to recover prior context
62
+ 3. Only THEN continue working
63
+
64
+ Do not skip step 1. Without it, everything done before compaction is lost.