@remnic/plugin-claude-code 1.0.0 → 9.3.515

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remnic",
3
3
  "description": "Universal memory for AI agents — automatic recall, observation, and cross-agent knowledge sharing",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "author": "Joshua Warren",
6
6
  "homepage": "https://github.com/joshuaswarren/remnic",
7
7
  "repository": "https://github.com/joshuaswarren/remnic"
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @remnic/plugin-claude-code
2
+
3
+ Native [Claude Code](https://docs.claude.com/en/docs/claude-code) plugin for [Remnic](https://github.com/joshuaswarren/remnic) memory. Wires Claude Code's session hooks, MCP server, skills, and the `memory-review` agent into a running Remnic daemon so every Claude Code session gets persistent long-term memory automatically.
4
+
5
+ ## Install
6
+
7
+ Three discrete steps. None is automated for you end-to-end today; each writes to a different place.
8
+
9
+ 1. **Mint a Remnic-side bearer token and record the connector.**
10
+
11
+ ```bash
12
+ remnic connectors install claude-code
13
+ ```
14
+
15
+ This writes `~/.remnic/connectors/claude-code.json` (Remnic's connector-state file) and stores a bearer token in Remnic's token store. It does NOT touch any Claude Code configuration — the Claude Code memory-extension publisher in `@remnic/core` is a stub (`isHostAvailable()` → false, `publish()` → no-op), so no hook/skill/agent files are written and no Claude MCP config is edited.
16
+
17
+ 2. **Add Remnic as an MCP server in your Claude Code config.** Paste the `.mcp.json` block from the "MCP setup" section below into Claude Code's MCP config (commonly `~/.mcp.json` or `~/.claude.json`, per your Claude install), replacing `{{REMNIC_TOKEN}}` with the token minted in step 1. Without this step Claude Code has no way to talk to the Remnic daemon.
18
+
19
+ 3. **Install this package and load it through Claude Code's plugin system** so the hook scripts, skills, and `memory-review` agent are actually active:
20
+
21
+ ```bash
22
+ npm install -g @remnic/plugin-claude-code
23
+ ```
24
+
25
+ Consult Claude Code's plugin docs for the exact load mechanism your install supports (plugin marketplace / symlink / etc.). Until this step runs, auto-recall and auto-observe don't fire even if step 2 is correct.
26
+
27
+ ## What ships
28
+
29
+ The package is **data-only** (no JavaScript runtime) — it's a bundle of Claude Code configuration:
30
+
31
+ | File / dir | Purpose |
32
+ |---|---|
33
+ | `.claude-plugin/plugin.json` | Plugin manifest |
34
+ | `hooks/hooks.json` | `SessionStart`, `PostToolUse` (Write/Edit/MultiEdit), and `UserPromptSubmit` hook wiring |
35
+ | `hooks/bin/*.sh` | Small shell scripts that call the local Remnic daemon |
36
+ | `skills/` | `remnic-recall`, `remnic-remember`, `remnic-search`, `remnic-status`, `remnic-entities`, `remnic-memory-workflow` |
37
+ | `agents/memory-review.md` | Subagent that audits + summarizes memory for long-running sessions |
38
+ | `.mcp.json` | MCP server config pointing Claude Code at `http://localhost:4318/mcp` |
39
+ | `settings.json` | Default Claude Code settings for the plugin |
40
+
41
+ ## What you get at runtime
42
+
43
+ Once installed and a Remnic daemon is running (`remnic daemon start`):
44
+
45
+ - **Auto-recall** on `SessionStart` and on every `UserPromptSubmit` — relevant memories are injected into the session prompt before Claude Code's first turn and before each subsequent user turn.
46
+ - **Auto-observe** on `PostToolUse` for `Write`, `Edit`, and `MultiEdit` tools — new facts, decisions, and entities touched by file edits are buffered for extraction without the user lifting a finger.
47
+ - **Memory skills** — invoke `/remnic-recall`, `/remnic-search`, `/remnic-remember`, `/remnic-entities`, `/remnic-status` directly in Claude Code chats.
48
+ - **Cross-agent sharing** — the same memory store is shared with every other Remnic-connected agent (Codex, OpenClaw, Replit, Hermes, etc.), so what one agent learns is available to all.
49
+
50
+ ## MCP setup
51
+
52
+ The plugin expects a Remnic daemon reachable at `http://localhost:4318/mcp` with a bearer token. `remnic connectors install claude-code` does NOT write this for you — the Claude Code publisher in `@remnic/core` is a stub, so no Claude MCP config is touched. You must paste the following `.mcp.json` block into Claude Code's MCP config by hand (step 2 of the Install flow above):
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "remnic": {
58
+ "type": "http",
59
+ "url": "http://localhost:4318/mcp",
60
+ "headers": {
61
+ "Authorization": "Bearer {{REMNIC_TOKEN}}",
62
+ "X-Engram-Client-Id": "claude-code"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ Replace `{{REMNIC_TOKEN}}` with a token minted via `remnic token generate <connector-id>`.
70
+
71
+ ## Agent note
72
+
73
+ If you're an AI agent scaffolding a Claude Code integration: **do not** hand-edit hook scripts in a user's `~/.claude/` tree. The full setup has two components:
74
+
75
+ 1. `remnic connectors install claude-code` mints the MCP token and writes Remnic-side connector config. This does NOT deploy hooks/skills/agents — Claude Code doesn't yet expose a file-based extension directory, so the corresponding publisher in `@remnic/core` is a stub.
76
+ 2. Install this npm package and load it through Claude Code's plugin system so the hook/skill/agent tree is picked up. Until both steps run, auto-recall and auto-observe will not fire even though `remnic connectors doctor claude-code` reports green.
77
+
78
+ The plugin is intentionally data-only so Claude Code's plugin loader can manage upgrades atomically.
79
+
80
+ ## Related
81
+
82
+ - [`@remnic/cli`](https://www.npmjs.com/package/@remnic/cli) — daemon lifecycle + installer
83
+ - [`@remnic/plugin-codex`](https://www.npmjs.com/package/@remnic/plugin-codex) — same idea, for OpenAI Codex CLI
84
+ - [`@remnic/plugin-openclaw`](https://www.npmjs.com/package/@remnic/plugin-openclaw) — OpenClaw memory-slot plugin
85
+ - Connector guide: [docs/integration/connector-setup.md](https://github.com/joshuaswarren/remnic/blob/main/docs/integration/connector-setup.md) in the repo
86
+ - Source + issues: <https://github.com/joshuaswarren/remnic>
87
+
88
+ ## License
89
+
90
+ MIT. See the root [LICENSE](https://github.com/joshuaswarren/remnic/blob/main/LICENSE) file.
@@ -56,7 +56,87 @@ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write
56
56
  CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd||'')" "$INPUT" 2>/dev/null || echo "")"
57
57
  PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
58
58
 
59
- log "session=$SESSION_ID project=$PROJECT_NAME"
59
+ # Resolve git context for the session's cwd (issue #569 PR 5). Produces
60
+ # either a JSON object for the `codingContext` field, or an empty string
61
+ # when the cwd is not inside a git repo. All git calls are wrapped in &&
62
+ # so any failure silently drops back to no-context.
63
+ CODING_CONTEXT_JSON=""
64
+ if [ -n "$CWD" ] && [ -d "$CWD" ] && command -v git >/dev/null 2>&1; then
65
+ # `git` calls are short-timeout and local. Any failure → empty.
66
+ REMNIC_GIT_TOP="$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null || echo "")"
67
+ if [ -n "$REMNIC_GIT_TOP" ]; then
68
+ REMNIC_GIT_BRANCH="$(git -C "$REMNIC_GIT_TOP" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")"
69
+ [ "$REMNIC_GIT_BRANCH" = "HEAD" ] && REMNIC_GIT_BRANCH=""
70
+ REMNIC_GIT_ORIGIN="$(git -C "$REMNIC_GIT_TOP" remote get-url origin 2>/dev/null || echo "")"
71
+ REMNIC_GIT_DEFAULT_BRANCH="$(git -C "$REMNIC_GIT_TOP" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/origin/||' || echo "")"
72
+ CODING_CONTEXT_JSON="$(REMNIC_GIT_TOP="$REMNIC_GIT_TOP" REMNIC_GIT_BRANCH="$REMNIC_GIT_BRANCH" REMNIC_GIT_ORIGIN="$REMNIC_GIT_ORIGIN" REMNIC_GIT_DEFAULT_BRANCH="$REMNIC_GIT_DEFAULT_BRANCH" node -e "
73
+ // Mirror the pure logic from @remnic/core's resolveGitContext so the
74
+ // hook produces the same projectId without calling into the daemon
75
+ // first. FNV-1a 32-bit stable hash.
76
+ const rootPath = process.env.REMNIC_GIT_TOP || '';
77
+ const branch = process.env.REMNIC_GIT_BRANCH || null;
78
+ const origin = process.env.REMNIC_GIT_ORIGIN || '';
79
+ const defaultBranch = process.env.REMNIC_GIT_DEFAULT_BRANCH || null;
80
+ function stableHash(input) {
81
+ let hash = 0x811c9dc5;
82
+ for (let i = 0; i < input.length; i++) {
83
+ hash ^= input.charCodeAt(i);
84
+ hash = Math.imul(hash, 0x01000193) >>> 0;
85
+ }
86
+ return hash.toString(16).padStart(8, '0');
87
+ }
88
+ // Mirrors packages/remnic-core/src/coding/git-context.ts
89
+ // normalizeOriginUrl. Keep the two in sync so the hook-computed
90
+ // projectId matches what the daemon computes on the same origin.
91
+ function normalizeOriginUrl(raw) {
92
+ let u = (raw || '').trim();
93
+ if (!u) return '';
94
+ // Case-insensitive .git strip — matches the TS canonical form.
95
+ if (/\\.git\$/i.test(u)) u = u.slice(0, -4);
96
+ // Windows drive-letter: short-circuit scp parsing.
97
+ if (/^[A-Za-z]:[\\\\/]/.test(u)) return u.toLowerCase();
98
+ // Protocol form: handles ssh://, https://, file:///, bracketed
99
+ // IPv6 hosts, optional user, optional port, and empty host
100
+ // (file:///path).
101
+ const proto = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^@/]+@)?(\\[[^\\]]+\\]|[^/:]*)(?::(\\d+))?(\\/.*)?\$/i.exec(u);
102
+ if (proto) {
103
+ let host = proto[1] || '';
104
+ const wasBracketed = host.startsWith('[') && host.endsWith(']');
105
+ if (wasBracketed) host = host.slice(1, -1);
106
+ const port = proto[2];
107
+ const p = (proto[3] || '').replace(/^\\/+/, '');
108
+ const hostPort = port
109
+ ? (wasBracketed ? '[' + host + ']:' + port : host + ':' + port)
110
+ : host;
111
+ const prefix = hostPort.length > 0 ? hostPort : 'localhost';
112
+ return (prefix + '/' + p).toLowerCase();
113
+ }
114
+ // scp form: [user@]host:path — user@ optional, bracketed IPv6 host
115
+ // supported. A matched path starting with // is a protocol-URL
116
+ // leftover and is rejected.
117
+ const scp = /^(?:([^@\\s\\/]+)@)?(\\[[^\\]]+\\]|[^:@\\s\\/]+):(.+)\$/.exec(u);
118
+ if (scp) {
119
+ let host = scp[2] || '';
120
+ if (host.startsWith('[') && host.endsWith(']')) host = host.slice(1, -1);
121
+ const p = scp[3] || '';
122
+ if (p.startsWith('//')) return u.toLowerCase();
123
+ return (host + '/' + p.replace(/^\\/+/, '')).toLowerCase();
124
+ }
125
+ return u.toLowerCase();
126
+ }
127
+ const normalized = normalizeOriginUrl(origin);
128
+ const projectId = normalized ? 'origin:' + stableHash(normalized) : 'root:' + stableHash(rootPath);
129
+ process.stdout.write(JSON.stringify({
130
+ projectId,
131
+ branch: branch || null,
132
+ rootPath,
133
+ defaultBranch: defaultBranch || null,
134
+ }));
135
+ " 2>/dev/null || echo "")"
136
+ fi
137
+ fi
138
+
139
+ log "session=$SESSION_ID project=$PROJECT_NAME coding-context=${CODING_CONTEXT_JSON:+yes}"
60
140
 
61
141
  # Health check — start daemon if not running
62
142
  if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
@@ -82,12 +162,29 @@ fi
82
162
 
83
163
  QUERY="Starting a new coding session in project: ${PROJECT_NAME}. Recall relevant memories, preferences, decisions, patterns, and context about this project and the user."
84
164
 
85
- REQUEST_BODY="$(node -e "process.stdout.write(JSON.stringify({
86
- query: process.argv[1],
87
- sessionKey: process.argv[2],
88
- topK: 12,
89
- mode: 'auto'
90
- }))" "$QUERY" "$SESSION_ID" 2>/dev/null)"
165
+ REQUEST_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
166
+ const body = {
167
+ query: process.argv[1],
168
+ sessionKey: process.argv[2],
169
+ topK: 12,
170
+ mode: 'auto',
171
+ };
172
+ const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
173
+ if (raw) {
174
+ try { body.codingContext = JSON.parse(raw); } catch (_) {
175
+ // Context envelope was provided but failed to parse. Explicitly
176
+ // clear any previously-attached context for this session so a
177
+ // malformed envelope does not silently keep stale state.
178
+ body.codingContext = null;
179
+ }
180
+ } else {
181
+ // No git context resolvable for this cwd. Explicitly clear any
182
+ // previously-attached context so a session that moves out of a repo
183
+ // does not keep routing to the old project namespace.
184
+ body.codingContext = null;
185
+ }
186
+ process.stdout.write(JSON.stringify(body));
187
+ " "$QUERY" "$SESSION_ID" 2>/dev/null)"
91
188
 
92
189
  [ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
93
190
 
@@ -104,12 +201,19 @@ RESPONSE="$(echo "$RAW" | sed '$d')"
104
201
 
105
202
  if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
106
203
  log "full recall failed (curl=$CURL_EXIT http=$HTTP_STATUS) — falling back to minimal"
107
- MINIMAL_BODY="$(node -e "process.stdout.write(JSON.stringify({
108
- query: process.argv[1],
109
- sessionKey: process.argv[2],
110
- topK: 8,
111
- mode: 'minimal'
112
- }))" "$QUERY" "$SESSION_ID" 2>/dev/null)"
204
+ MINIMAL_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
205
+ const body = {
206
+ query: process.argv[1],
207
+ sessionKey: process.argv[2],
208
+ topK: 8,
209
+ mode: 'minimal',
210
+ };
211
+ const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
212
+ if (raw) {
213
+ try { body.codingContext = JSON.parse(raw); } catch (_) { /* ignore */ }
214
+ }
215
+ process.stdout.write(JSON.stringify(body));
216
+ " "$QUERY" "$SESSION_ID" 2>/dev/null)"
113
217
  RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
114
218
  -X POST "$REMNIC_URL" \
115
219
  -H "Authorization: Bearer ${REMNIC_TOKEN}" \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/plugin-claude-code",
3
- "version": "1.0.0",
3
+ "version": "9.3.515",
4
4
  "description": "Remnic memory plugin for Claude Code — hooks, skills, MCP integration",
5
5
  "type": "module",
6
6
  "license": "MIT",