Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 70 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Your agent remembers what you worked on - across sessions, across projects.
- **Team Memory** — Project knowledge shared across your team, separate from personal memories
- **Auto Capture** — Conversations saved when session ends
- **Project Config** — Per-repo settings, API keys, and container tags
- **Custom Container Tags** — Define custom memory containers (e.g., `work`, `personal`, `code_style`). The AI automatically routes memories to the right container based on your descriptions

## Installation

Expand All @@ -31,6 +32,8 @@ export SUPERMEMORY_CC_API_KEY="sm_..."
- **super-search** — Ask about past work or previous sessions, Claude searches your memories
- **super-save** — Ask to save something important, Claude saves it for the team

All memory commands support `--container <tag>` to target a specific custom container when custom containers are enabled.

## Commands

| Command | Description |
Expand Down Expand Up @@ -60,13 +63,16 @@ SUPERMEMORY_DEBUG=true # Optional: enable debug logging
}
```

| Option | Description |
| ------------------- | --------------------------------------------- |
| `maxProfileItems` | Max memories in context (default: 5) |
| `signalExtraction` | Only capture important turns (default: false) |
| `signalKeywords` | Keywords that trigger capture |
| `signalTurnsBefore` | Context turns before signal (default: 3) |
| `includeTools` | Tools to explicitly capture |
| Option | Description |
| ---------------------------- | --------------------------------------------- |
| `maxProfileItems` | Max memories in context (default: 5) |
| `signalExtraction` | Only capture important turns (default: false) |
| `signalKeywords` | Keywords that trigger capture |
| `signalTurnsBefore` | Context turns before signal (default: 3) |
| `includeTools` | Tools to explicitly capture |
| `enableCustomContainers` | Enable AI-driven container routing (default: false) |
| `customContainers` | Array of `{tag, description}` container definitions |
| `customContainerInstructions`| Free-text instructions for AI on routing |

**Project Config** — `.claude/.supermemory-claude/config.json`

Expand All @@ -80,11 +86,63 @@ Per-repo overrides. Run `/claude-supermemory:project-config` or create manually:
}
```

| Option | Description |
| ---------------------- | --------------------------- |
| `apiKey` | Project-specific API key |
| `personalContainerTag` | Override personal container |
| `repoContainerTag` | Override team container tag |
| Option | Description |
| ---------------------------- | ------------------------------------ |
| `apiKey` | Project-specific API key |
| `personalContainerTag` | Override personal container |
| `repoContainerTag` | Override team container tag |
| `enableCustomContainers` | Enable custom container routing |
| `customContainers` | Project-specific container definitions |
| `customContainerInstructions`| Project-specific routing instructions |

## Custom Container Tags

Custom container tags let you organize memories into separate buckets (e.g., `work`,
`personal`, `code_style`). The AI reads the container descriptions from your config
and automatically picks the right container when saving memories.

### Setup

Add these fields to `~/.supermemory-claude/settings.json`:

```json
{
"enableCustomContainers": true,
"customContainers": [
{ "tag": "personal", "description": "Personal life — family, health, hobbies, routines" },
{ "tag": "work", "description": "Work-related — projects, deadlines, meetings, colleagues" },
{ "tag": "code_style", "description": "Coding preferences — languages, tools, patterns, conventions" }
],
"customContainerInstructions": "Route coding preferences to code_style. Personal topics to personal. Default to project container for ambiguous content."
}
```

You can also set these per-project in `.claude/.supermemory-claude/config.json`.

### How it works

1. You define containers with a `tag` (identifier) and a `description` (plain English
explaining what belongs there).
2. On session start, the container catalog is injected into the AI's context so it knows
what containers are available.
3. When the AI saves a memory, it picks the best matching container based on the
descriptions and uses `--container <tag>`.
4. When searching, the AI can also target specific containers.
5. Auto-capture (background saving at session end) always goes to the default
personal/repo containers — only explicit saves get routed to custom containers.
6. Invalid container tags are rejected with a list of valid options, preventing
orphaned spaces.

Each container tag automatically becomes a **Space** on the
[Supermemory dashboard](https://app.supermemory.ai), so you can view and manage
memories organized by category.

### Container config reference

| Field | Type | Description |
| ------------------ | -------- | -------------------------------------------------- |
| `tag` | `string` | Unique identifier for the container (e.g. `work`). |
| `description` | `string` | Plain English description for AI routing. |

## License

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-supermemory-dev",
"version": "2.0.1",
"version": "2.1.0",
"description": "Claude code plugin by Supermemory AI",
"private": true,
"type": "commonjs",
Expand Down
13 changes: 7 additions & 6 deletions plugin/scripts/add-memory.cjs

Large diffs are not rendered by default.

69 changes: 40 additions & 29 deletions plugin/scripts/context-hook.cjs

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions plugin/scripts/save-project-memory.cjs

Large diffs are not rendered by default.

41 changes: 21 additions & 20 deletions plugin/scripts/search-memory.cjs

Large diffs are not rendered by default.

31 changes: 16 additions & 15 deletions plugin/scripts/summary-hook.cjs

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion plugin/skills/super-save/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,17 @@ Keep it natural. Capture the conversation flow.
## Step 3: Save

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/save-project-memory.cjs" "FORMATTED_CONTENT"
node "${CLAUDE_PLUGIN_ROOT}/scripts/save-project-memory.cjs" [--container <tag>] "FORMATTED_CONTENT"
```

### Container Routing

If the user specifies a container (e.g. "save into code_preferences"), use `--container <tag>`:

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/save-project-memory.cjs" --container code_preferences "FORMATTED_CONTENT"
```

If no container is specified, memories go to the default project container.

Available containers are listed in the `<supermemory-containers>` section of session context (if enabled).
6 changes: 6 additions & 0 deletions plugin/skills/super-search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" [--user|--repo|--both] "U
- `--both` (default): Search both personal session and project memories across team members in parallel
- `--user`: Search personal/user memories across sessions
- `--repo`: Search project/repo memories across team members
- `--container <tag>`: Search a specific custom container (e.g. `code_preferences`, `personal`, `architecture`)

## Examples

Expand All @@ -41,6 +42,11 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" [--user|--repo|--both] "U
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" --user "coding preferences style"
```

- User asks to search a specific container:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" --container code_preferences "TypeScript formatting"
```

## Present Results

The script outputs formatted memory results with timestamps and relevance scores. Present them clearly to the user and offer to search again with different terms if needed.
31 changes: 24 additions & 7 deletions src/add-memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ const {
PERSONAL_ENTITY_CONTEXT,
} = require('./lib/supermemory-client');
const { getContainerTag, getProjectName } = require('./lib/container-tag');
const { loadSettings, getApiKey } = require('./lib/settings');
const {
loadSettings,
getApiKey,
validateContainerTag,
} = require('./lib/settings');
const { getUserFriendlyError } = require('./lib/error-helpers');
const { parseMemoryArgs } = require('./lib/parse-args');

async function main() {
const content = process.argv.slice(2).join(' ');
const { content, containerTag } = parseMemoryArgs(process.argv.slice(2));

if (!content || !content.trim()) {
console.log(
'No content provided. Usage: node add-memory.cjs "content to save"',
'No content provided. Usage: node add-memory.cjs [--container <tag>] "content to save"',
);
return;
}
Expand All @@ -28,14 +33,23 @@ async function main() {
}

const cwd = process.cwd();
const containerTag = getContainerTag(cwd);

if (containerTag) {
const validationError = validateContainerTag(containerTag, cwd);
if (validationError) {
console.log(validationError);
process.exit(1);
}
}

const effectiveTag = containerTag || getContainerTag(cwd);
const projectName = getProjectName(cwd);

try {
const client = new SupermemoryClient(apiKey, containerTag);
const client = new SupermemoryClient(apiKey, effectiveTag);
const result = await client.addMemory(
content,
containerTag,
effectiveTag,
{
type: 'manual',
project: projectName,
Expand All @@ -44,7 +58,10 @@ async function main() {
{ entityContext: PERSONAL_ENTITY_CONTEXT },
);

console.log(`Memory saved to project: ${projectName}`);
const tagLabel = containerTag
? `container '${containerTag}'`
: `project: ${projectName}`;
console.log(`Memory saved to ${tagLabel}`);
console.log(`ID: ${result.id}`);
} catch (err) {
console.log(`Error saving memory: ${getUserFriendlyError(err)}`);
Expand Down
51 changes: 35 additions & 16 deletions src/context-hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ const {
getRepoContainerTag,
getProjectName,
} = require('./lib/container-tag');
const { loadSettings, getApiKey, debugLog } = require('./lib/settings');
const {
loadSettings,
getApiKey,
debugLog,
getContainerCatalog,
} = require('./lib/settings');
const { readStdin, writeOutput } = require('./lib/stdin');
const { startAuthFlow, AUTH_BASE_URL } = require('./lib/auth');
const { formatContext, combineContexts } = require('./lib/format-context');
Expand Down Expand Up @@ -73,9 +78,7 @@ Or set SUPERMEMORY_CC_API_KEY environment variable manually.
client
.getProfile(personalTag, projectName)
.catch(handleProfileError('personal')),
client
.getProfile(repoTag, projectName)
.catch(handleProfileError('repo')),
client.getProfile(repoTag, projectName).catch(handleProfileError('repo')),
]);

const personalContext = formatContext(
Expand Down Expand Up @@ -107,31 +110,47 @@ Or set SUPERMEMORY_CC_API_KEY environment variable manually.
? `<supermemory-status>\n${[...new Set(apiErrors)].join('\n')}\n</supermemory-status>\n`
: '';

const containerCatalog = getContainerCatalog(cwd);
let containerSection = '';
if (containerCatalog) {
containerSection = `\n<supermemory-containers>\n${containerCatalog}\n</supermemory-containers>`;
}

if (!additionalContext) {
writeOutput({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: apiErrors.length > 0
? errorNotice
: `<supermemory-context>
No previous memories found for this project.
Memories will be saved as you work.
</supermemory-context>`,
},
});
if (containerCatalog) {
writeOutput({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext:
errorNotice +
`<supermemory-context>\nNo previous memories found for this project.\nMemories will be saved as you work.\n</supermemory-context>` +
Comment thread
ved015 marked this conversation as resolved.
containerSection,
},
});
} else {
writeOutput({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext:
errorNotice +
`<supermemory-context>\nNo previous memories found for this project.\nMemories will be saved as you work.\n</supermemory-context>`,
},
});
}
return;
}

debugLog(settings, 'Context generated', {
length: additionalContext.length,
hasPersonal: !!personalContext,
hasRepo: !!repoContext,
hasContainerCatalog: !!containerCatalog,
});

writeOutput({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: errorNotice + additionalContext,
additionalContext: errorNotice + additionalContext + containerSection,
},
});
} catch (err) {
Expand Down
8 changes: 4 additions & 4 deletions src/lib/error-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @returns {string}
*/
function getUserFriendlyError(err) {
const status = err && err.status;
const status = err?.status;

if (status === 400) {
return 'Bad request \u2014 your API key or request format may be invalid. Check your key at https://console.supermemory.ai';
Expand All @@ -31,7 +31,7 @@ function getUserFriendlyError(err) {
return 'Supermemory service is temporarily unavailable. Will retry next session.';
}

return (err && err.message) || 'Unknown error';
return err?.message || 'Unknown error';
}

/**
Expand All @@ -44,7 +44,7 @@ function getUserFriendlyError(err) {
* @returns {boolean}
*/
function isRetryableError(err) {
const status = err && err.status;
const status = err?.status;
if (status === 429) return true;
if (typeof status === 'number' && status >= 500) return true;
// Connection / timeout errors have no status
Expand All @@ -62,7 +62,7 @@ function isRetryableError(err) {
* @returns {boolean}
*/
function isBenignError(err) {
const status = err && err.status;
const status = err?.status;
if (status === 404) return true;
// No status usually means a connection or timeout error
if (status === undefined || status === null) return true;
Expand Down
41 changes: 41 additions & 0 deletions src/lib/parse-args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Parses --container <tag> from argv; remaining args become the content.
function parseMemoryArgs(args) {
let containerTag = null;
const contentParts = [];

for (let i = 0; i < args.length; i++) {
if (args[i] === '--container' && i + 1 < args.length) {
containerTag = args[++i];
} else {
contentParts.push(args[i]);
}
}

return { content: contentParts.join(' '), containerTag };
}

// Parses --user/--repo/--both/--container <tag> from argv; remaining args become the query.
function parseSearchArgs(args) {
let containerType = 'both';
let containerTag = null;
const queryParts = [];

for (let i = 0; i < args.length; i++) {
if (args[i] === '--user') {
containerType = 'user';
} else if (args[i] === '--repo') {
containerType = 'repo';
} else if (args[i] === '--both') {
containerType = 'both';
} else if (args[i] === '--container' && i + 1 < args.length) {
containerTag = args[++i];
containerType = 'custom';
} else {
queryParts.push(args[i]);
}
}

return { containerType, query: queryParts.join(' '), containerTag };
}

module.exports = { parseMemoryArgs, parseSearchArgs };
Loading
Loading