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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,38 @@ API key precedence is `SUPERMEMORY_CC_API_KEY`, project `apiKey`, then
| `namespace` | Canonical project scope, preserving the exact old container-tag value. |
| `repoContainerTag` | Override the unified project container tag. Checked before `SUPERMEMORY_REPO_TAG`. |

### Several organizations

Each OAuth login is for one organization, picked on the consent screen. Log in
again to add another; the first one stays the default.

```bash
node plugin/hooks/lib/auth.js login # add an organization
node plugin/hooks/lib/auth.js status # list them (no secrets)
node plugin/hooks/lib/auth.js use <org-id> # change the default
node plugin/hooks/lib/auth.js logout --org <id> # forget one organization
```

Which one a directory uses comes from the rules every Supermemory agent shares:
`SUPERMEMORY_ORG`, `projects` rules in `~/.supermemory/config.json`, the
repository's committed `.supermemory/config.json`, then the default. For
example, to send every repo of a GitHub org to your team:

```json
{
"projects": [
{ "match": { "repo": "github.com/acme/*" }, "organization": "<team-org-id>" }
]
}
```

If a directory belongs to an organization this plugin isn't logged in to, or
whose login the API rejected, memory is paused there: nothing is saved or
recalled, and no other organization is used instead. Session start opens the
consent screen so you can add it, at most every 12 hours per organization.
Logging in from inside a repository that has no rule yet binds it to the
organization you picked.

### Advanced self-hosted compatibility

Hosted Supermemory uses v5 without a version choice. Existing custom REST URLs
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"test": "node --test test/unit.mjs",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"format": "biome format --write ."
"format": "biome format --write .",
"build:agent-config": "node scripts/build-agent-config.mjs"
},
"engines": {
"node": ">=18.0.0"
Expand Down
13 changes: 13 additions & 0 deletions plugin/hooks/lib/api.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { reportUnauthorized } = require('./auth');
const { resolveApiVersion } = require('./settings');

const AGENT_SUPPORTING_CONTEXT = `Shared coding-agent memory for one software repository.
Expand Down Expand Up @@ -42,6 +43,7 @@ async function post(
signal: AbortSignal.timeout(timeoutMs),
});

if (response.status === 401) await reportUnauthorized(apiKey);
if (!response.ok) {
const text = await response.text().catch(() => '');
throw Object.assign(
Expand Down Expand Up @@ -69,10 +71,21 @@ function useV5(baseUrl, options, namespace) {
return true;
}

// A 401 tells the auth layer which OAuth org refused us, so routing can pause
// that org instead of retrying it forever.
function reportingFetch(apiKey) {
return async (...args) => {
const response = await fetch(...args);
if (response.status === 401) await reportUnauthorized(apiKey);
return response;
};
}

function sdkClient(baseUrl, apiKey, options) {
const { Supermemory } = require('../vendor/supermemory.cjs');
return new Supermemory({
apiKey,
fetch: reportingFetch(apiKey),
baseUrl: baseUrl.replace(/\/+$/, ''),
headers: { 'x-sm-source': 'claude-code' },
timeoutInSeconds: (options.timeoutMs ?? REQUEST_TIMEOUT_MS) / 1000,
Expand Down
Loading
Loading