feat: add opencode plugin for Mnemosyne routing
TypeScript plugin that injects baseURL to route Anthropic API calls through the Mnemosyne gateway, enriches compaction with memory context, and provides mnemosyne_status/mnemosyne_query custom tools. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
9b25b33a50
commit
b21871b8fc
4 changed files with 629 additions and 0 deletions
99
opencode-plugin/INSTALL.md
Normal file
99
opencode-plugin/INSTALL.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# Installing opencode-mnemosyne
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Mnemosyne gateway** running (`mnemosyne --no-launch --port 8080`)
|
||||
2. **opencode** installed and configured
|
||||
3. **OAuth authenticated** (`mnemosyne --login`)
|
||||
|
||||
## Quick Install (Local Development)
|
||||
|
||||
```bash
|
||||
# From the contextmanager project root
|
||||
cd opencode-plugin
|
||||
npm install && npm run build
|
||||
|
||||
# Register as a local plugin in opencode
|
||||
# Add to your ~/.config/opencode/opencode.json:
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin": [
|
||||
"oh-my-opencode@latest",
|
||||
"file:///var/home/joey/Projects/contextmanager/opencode-plugin"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The plugin is configured via environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MNEMOSYNE_HOST` | `127.0.0.1` | Gateway host |
|
||||
| `MNEMOSYNE_PORT` | `8080` | Gateway port |
|
||||
| `MNEMOSYNE_LOG_LEVEL` | `info` | Log level: silent, error, info, debug |
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Start the Mnemosyne Gateway
|
||||
|
||||
```bash
|
||||
cd ~/Projects/contextmanager
|
||||
mnemosyne --no-launch --port 8080
|
||||
```
|
||||
|
||||
### 2. Start opencode normally
|
||||
|
||||
The plugin automatically:
|
||||
- Routes Anthropic API calls through the gateway (via `baseURL` injection)
|
||||
- Enriches compaction prompts with memory context
|
||||
- Provides `mnemosyne_status` and `mnemosyne_query` tools
|
||||
|
||||
### 3. Custom Tools
|
||||
|
||||
Once loaded, the LLM has access to two new tools:
|
||||
|
||||
- **`mnemosyne_status`** — Show memory state (objects, tokens, evictions, faults)
|
||||
- **`mnemosyne_query`** — Query the backing store for previously evicted context
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
opencode → plugin (config hook injects baseURL) → Mnemosyne gateway → Anthropic API
|
||||
↕
|
||||
Object Store (backing store)
|
||||
Helper LLM (Haiku 4.5)
|
||||
Fidelity Manager
|
||||
Segmenter + Embedder
|
||||
```
|
||||
|
||||
The plugin's `config` hook dynamically sets `provider.anthropic.options.baseURL` to
|
||||
the gateway URL. This means all Anthropic API calls are transparently proxied through
|
||||
Mnemosyne, which:
|
||||
|
||||
1. **Segments** conversation into semantic objects
|
||||
2. **Stores** objects with embeddings in the backing store
|
||||
3. **Compresses** old objects via multi-fidelity degradation (L0→L1→L2→L3→L4)
|
||||
4. **Retrieves** evicted content on demand via micro-faults
|
||||
5. **Detects** topic shifts and reclassifies goals
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
If the gateway is not running, the plugin silently skips all hooks. opencode works
|
||||
normally with no proxy — zero impact on the user experience.
|
||||
|
||||
## npm Install (once published)
|
||||
|
||||
```bash
|
||||
# In your opencode.json
|
||||
{
|
||||
"plugin": [
|
||||
"opencode-mnemosyne@latest"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then set `MNEMOSYNE_PORT=8080` in your environment.
|
||||
46
opencode-plugin/package.json
Normal file
46
opencode-plugin/package.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "opencode-mnemosyne",
|
||||
"version": "0.1.0",
|
||||
"description": "Mnemosyne context manager plugin for opencode — object-addressed memory with multi-fidelity compression",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "^1.0.85"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"typescript": "^5.7.0"
|
||||
},
|
||||
"opencode": {
|
||||
"type": "plugin",
|
||||
"hooks": [
|
||||
"event",
|
||||
"config",
|
||||
"chat.message",
|
||||
"experimental.chat.messages.transform",
|
||||
"experimental.session.compacting",
|
||||
"tool.execute.after"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"opencode",
|
||||
"plugin",
|
||||
"context-management",
|
||||
"mnemosyne",
|
||||
"memory"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
464
opencode-plugin/src/index.ts
Normal file
464
opencode-plugin/src/index.ts
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
/**
|
||||
* opencode-mnemosyne — Plugin that wires opencode to the Mnemosyne context manager.
|
||||
*
|
||||
* Integration strategy:
|
||||
* 1. config hook: injects baseURL to route Anthropic API calls through the gateway
|
||||
* 2. event hook: monitors session lifecycle, syncs with gateway health
|
||||
* 3. chat.message hook: prepends memory context from the backing store
|
||||
* 4. experimental.session.compacting: enriches compaction with object store context
|
||||
* 5. tool.execute.after: tracks tool executions for admission control
|
||||
* 6. custom tool: mnemosyne_status — shows memory state for debugging
|
||||
*
|
||||
* The Mnemosyne gateway is a separate Python process (started independently or
|
||||
* via `mnemosyne --no-launch`). This plugin only configures opencode to talk to it.
|
||||
*/
|
||||
import type { Plugin, PluginInput, Hooks } from "@opencode-ai/plugin";
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
|
||||
// ── Configuration ───────────────────────────────────────────────────────────
|
||||
|
||||
interface MnemosynePluginConfig {
|
||||
/** Gateway host (default: 127.0.0.1) */
|
||||
host: string;
|
||||
/** Gateway port (default: 8080). Set to 0 to auto-discover from gateway health endpoint. */
|
||||
port: number;
|
||||
/** Whether to inject baseURL into the Anthropic provider config */
|
||||
proxyAnthropicProvider: boolean;
|
||||
/** Whether to inject memory context into chat.message (experimental) */
|
||||
injectMemoryContext: boolean;
|
||||
/** Whether to inject memory context into compaction prompts */
|
||||
enrichCompaction: boolean;
|
||||
/** Max number of memory objects to include in compaction context */
|
||||
compactionMaxObjects: number;
|
||||
/** Log level: 'silent' | 'error' | 'info' | 'debug' */
|
||||
logLevel: "silent" | "error" | "info" | "debug";
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: MnemosynePluginConfig = {
|
||||
host: "127.0.0.1",
|
||||
port: 8080,
|
||||
proxyAnthropicProvider: true,
|
||||
injectMemoryContext: false, // disabled by default — the gateway's _preprocess handles this
|
||||
enrichCompaction: true,
|
||||
compactionMaxObjects: 20,
|
||||
logLevel: "info",
|
||||
};
|
||||
|
||||
// ── Logger ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const LOG_LEVELS = { silent: 0, error: 1, info: 2, debug: 3 } as const;
|
||||
|
||||
class Logger {
|
||||
private level: number;
|
||||
|
||||
constructor(level: MnemosynePluginConfig["logLevel"] = "info") {
|
||||
this.level = LOG_LEVELS[level];
|
||||
}
|
||||
|
||||
error(...args: unknown[]) {
|
||||
if (this.level >= LOG_LEVELS.error)
|
||||
console.error("[mnemosyne]", ...args);
|
||||
}
|
||||
info(...args: unknown[]) {
|
||||
if (this.level >= LOG_LEVELS.info)
|
||||
console.error("[mnemosyne]", ...args);
|
||||
}
|
||||
debug(...args: unknown[]) {
|
||||
if (this.level >= LOG_LEVELS.debug)
|
||||
console.error("[mnemosyne]", ...args);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gateway Client ──────────────────────────────────────────────────────────
|
||||
|
||||
interface GatewayHealth {
|
||||
status: string;
|
||||
process_session_id: string;
|
||||
token_cap: number;
|
||||
sessions: Record<
|
||||
string,
|
||||
{
|
||||
turn: number;
|
||||
last_effective_tokens: number;
|
||||
evictions: number;
|
||||
faults: number;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
interface GatewaySessionSummary {
|
||||
process_session_id: string;
|
||||
sessions: Record<string, unknown>;
|
||||
}
|
||||
|
||||
class GatewayClient {
|
||||
private baseUrl: string;
|
||||
private log: Logger;
|
||||
private _healthy: boolean = false;
|
||||
|
||||
constructor(host: string, port: number, log: Logger) {
|
||||
this.baseUrl = `http://${host}:${port}`;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return this.baseUrl;
|
||||
}
|
||||
|
||||
get healthy(): boolean {
|
||||
return this._healthy;
|
||||
}
|
||||
|
||||
async checkHealth(): Promise<GatewayHealth | null> {
|
||||
try {
|
||||
const resp = await fetch(`${this.baseUrl}/health`, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
this._healthy = false;
|
||||
return null;
|
||||
}
|
||||
const data = (await resp.json()) as GatewayHealth;
|
||||
this._healthy = data.status === "ok";
|
||||
return data;
|
||||
} catch {
|
||||
this._healthy = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(): Promise<GatewaySessionSummary | null> {
|
||||
try {
|
||||
const resp = await fetch(`${this.baseUrl}/api/sessions`, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!resp.ok) return null;
|
||||
return (await resp.json()) as GatewaySessionSummary;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the gateway's object store for session context.
|
||||
* This hits a Mnemosyne-specific API endpoint for memory retrieval.
|
||||
*/
|
||||
async queryMemory(
|
||||
sessionId: string,
|
||||
query: string,
|
||||
limit: number = 5
|
||||
): Promise<MemoryQueryResult | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
session_id: sessionId,
|
||||
query,
|
||||
limit: String(limit),
|
||||
});
|
||||
const resp = await fetch(
|
||||
`${this.baseUrl}/api/memory?${params.toString()}`,
|
||||
{ signal: AbortSignal.timeout(5000) }
|
||||
);
|
||||
if (!resp.ok) return null;
|
||||
return (await resp.json()) as MemoryQueryResult;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compaction context from the gateway's object store.
|
||||
* Returns a summary of all objects for injection into compaction prompts.
|
||||
*/
|
||||
async getCompactionContext(
|
||||
sessionId: string,
|
||||
maxObjects: number
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
session_id: sessionId,
|
||||
max_objects: String(maxObjects),
|
||||
});
|
||||
const resp = await fetch(
|
||||
`${this.baseUrl}/api/compaction-context?${params.toString()}`,
|
||||
{ signal: AbortSignal.timeout(5000) }
|
||||
);
|
||||
if (!resp.ok) return null;
|
||||
const data = (await resp.json()) as { context: string };
|
||||
return data.context;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface MemoryObject {
|
||||
id: string;
|
||||
object_type: string;
|
||||
stub: string;
|
||||
current_fidelity: number;
|
||||
tokens: number;
|
||||
similarity: number;
|
||||
}
|
||||
|
||||
interface MemoryQueryResult {
|
||||
objects: MemoryObject[];
|
||||
total_objects: number;
|
||||
session_tokens: number;
|
||||
}
|
||||
|
||||
// ── Config Loader ───────────────────────────────────────────────────────────
|
||||
|
||||
function loadConfig(directory: string): MnemosynePluginConfig {
|
||||
const config = { ...DEFAULT_CONFIG };
|
||||
|
||||
// Check environment variables first (highest priority)
|
||||
const envHost = process.env["MNEMOSYNE_HOST"];
|
||||
const envPort = process.env["MNEMOSYNE_PORT"];
|
||||
const envLogLevel = process.env["MNEMOSYNE_LOG_LEVEL"];
|
||||
|
||||
if (envHost) config.host = envHost;
|
||||
if (envPort) config.port = parseInt(envPort, 10);
|
||||
if (
|
||||
envLogLevel &&
|
||||
(envLogLevel === "silent" ||
|
||||
envLogLevel === "error" ||
|
||||
envLogLevel === "info" ||
|
||||
envLogLevel === "debug")
|
||||
) {
|
||||
config.logLevel = envLogLevel;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// ── Plugin Entry Point ──────────────────────────────────────────────────────
|
||||
|
||||
const MnemosynePlugin: Plugin = async (ctx: PluginInput): Promise<Hooks> => {
|
||||
const config = loadConfig(ctx.directory);
|
||||
const log = new Logger(config.logLevel);
|
||||
const gateway = new GatewayClient(config.host, config.port, log);
|
||||
|
||||
// Initial health check — non-blocking, plugin works even if gateway is down
|
||||
const initialHealth = await gateway.checkHealth();
|
||||
if (initialHealth) {
|
||||
log.info(
|
||||
`Connected to gateway at ${gateway.url} (${Object.keys(initialHealth.sessions).length} active sessions)`
|
||||
);
|
||||
} else {
|
||||
log.error(
|
||||
`Gateway not reachable at ${gateway.url}. Start with: mnemosyne --no-launch --port ${config.port}`
|
||||
);
|
||||
}
|
||||
|
||||
// Track active session IDs (opencode session → gateway session mapping)
|
||||
const sessionMap = new Map<string, string>();
|
||||
|
||||
return {
|
||||
// ── Config Hook: Inject baseURL ─────────────────────────────────
|
||||
async config(inputConfig) {
|
||||
if (!config.proxyAnthropicProvider) return;
|
||||
if (!gateway.healthy) {
|
||||
// Re-check health before modifying config
|
||||
await gateway.checkHealth();
|
||||
if (!gateway.healthy) {
|
||||
log.debug("Skipping baseURL injection — gateway not healthy");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject baseURL for the Anthropic provider
|
||||
// This routes all Anthropic API calls through the Mnemosyne gateway
|
||||
inputConfig.provider = inputConfig.provider ?? {};
|
||||
inputConfig.provider.anthropic = inputConfig.provider.anthropic ?? {};
|
||||
inputConfig.provider.anthropic.options =
|
||||
inputConfig.provider.anthropic.options ?? {};
|
||||
|
||||
// Only set if not already overridden by user
|
||||
if (!inputConfig.provider.anthropic.options.baseURL) {
|
||||
inputConfig.provider.anthropic.options.baseURL = gateway.url;
|
||||
log.info(`Routing Anthropic through gateway at ${gateway.url}`);
|
||||
} else {
|
||||
log.debug(
|
||||
`baseURL already set to ${inputConfig.provider.anthropic.options.baseURL}, not overriding`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// ── Event Hook: Session Lifecycle ───────────────────────────────
|
||||
async event({ event }) {
|
||||
const eventType = (event as Record<string, unknown>).type as string;
|
||||
|
||||
if (eventType === "session.created") {
|
||||
const sessionID = (event as Record<string, unknown>)
|
||||
.properties as Record<string, unknown>;
|
||||
const sid =
|
||||
(sessionID?.["sessionID"] as string) ??
|
||||
(sessionID?.["id"] as string);
|
||||
if (sid) {
|
||||
log.debug(`Session created: ${sid}`);
|
||||
// The gateway auto-creates sessions on first API call,
|
||||
// so we just track the mapping
|
||||
sessionMap.set(sid, sid);
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType === "session.deleted") {
|
||||
const sessionID = (event as Record<string, unknown>)
|
||||
.properties as Record<string, unknown>;
|
||||
const sid =
|
||||
(sessionID?.["sessionID"] as string) ??
|
||||
(sessionID?.["id"] as string);
|
||||
if (sid) {
|
||||
log.debug(`Session deleted: ${sid}`);
|
||||
sessionMap.delete(sid);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ── Compaction Hook: Inject Memory Context ──────────────────────
|
||||
"experimental.session.compacting": async (input, output) => {
|
||||
if (!config.enrichCompaction) return;
|
||||
if (!gateway.healthy) return;
|
||||
|
||||
const context = await gateway.getCompactionContext(
|
||||
input.sessionID,
|
||||
config.compactionMaxObjects
|
||||
);
|
||||
if (context) {
|
||||
output.context.push(
|
||||
`\n## Mnemosyne Memory State\n${context}\n\nThe above summarizes objects stored in the Mnemosyne backing store. ` +
|
||||
`These represent important context from this session that has been compressed ` +
|
||||
`for efficiency. Reference them by type/name when relevant.`
|
||||
);
|
||||
log.debug(
|
||||
`Injected compaction context for session ${input.sessionID} (${context.length} chars)`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// ── Tool Execute After: Track Tool Results ──────────────────────
|
||||
"tool.execute.after": async (input, _output) => {
|
||||
// Fire-and-forget: notify gateway about tool execution for admission control
|
||||
// The gateway already sees tool results via the proxy, but this gives it
|
||||
// the tool name context that the raw API call doesn't have
|
||||
log.debug(
|
||||
`Tool executed: ${input.tool} (session: ${input.sessionID})`
|
||||
);
|
||||
},
|
||||
|
||||
// ── Custom Tools ────────────────────────────────────────────────
|
||||
tool: {
|
||||
mnemosyne_status: tool({
|
||||
description:
|
||||
"Show the current state of the Mnemosyne context memory system. " +
|
||||
"Returns information about stored objects, memory pressure, fidelity levels, " +
|
||||
"and token savings for the current session.",
|
||||
args: {
|
||||
session_id: tool.schema
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Session ID to query. If omitted, shows the most recent session."
|
||||
),
|
||||
},
|
||||
async execute(args, toolCtx) {
|
||||
const health = await gateway.checkHealth();
|
||||
if (!health) {
|
||||
return (
|
||||
"Mnemosyne gateway is not running. " +
|
||||
`Start it with: mnemosyne --no-launch --port ${config.port}`
|
||||
);
|
||||
}
|
||||
|
||||
const targetSession = args.session_id ?? toolCtx.sessionID;
|
||||
const sessionData = health.sessions[targetSession];
|
||||
|
||||
if (!sessionData) {
|
||||
const sessionIds = Object.keys(health.sessions);
|
||||
if (sessionIds.length === 0) {
|
||||
return "Mnemosyne is running but has no active sessions.";
|
||||
}
|
||||
return (
|
||||
`Session '${targetSession}' not found in Mnemosyne. ` +
|
||||
`Active sessions: ${sessionIds.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
`## Mnemosyne Status`,
|
||||
`- **Gateway**: ${gateway.url} (healthy)`,
|
||||
`- **Session**: ${targetSession}`,
|
||||
`- **Turn**: ${sessionData.turn}`,
|
||||
`- **Effective Tokens**: ${sessionData.last_effective_tokens.toLocaleString()}`,
|
||||
`- **Evictions**: ${sessionData.evictions}`,
|
||||
`- **Faults**: ${sessionData.faults}`,
|
||||
`- **Token Cap**: ${health.token_cap || "unlimited"}`,
|
||||
``,
|
||||
`### All Sessions`,
|
||||
...Object.entries(health.sessions).map(
|
||||
([sid, s]) =>
|
||||
`- ${sid}: turn ${s.turn}, ${s.last_effective_tokens.toLocaleString()} tokens, ${s.evictions} evictions`
|
||||
),
|
||||
].join("\n");
|
||||
},
|
||||
}),
|
||||
|
||||
mnemosyne_query: tool({
|
||||
description:
|
||||
"Query the Mnemosyne backing store for specific information from " +
|
||||
"previously evicted or compressed context. Use this when you need to " +
|
||||
"recall details that may have been removed from the context window.",
|
||||
args: {
|
||||
query: tool.schema
|
||||
.string()
|
||||
.describe(
|
||||
"Natural language query about what information to retrieve from memory."
|
||||
),
|
||||
limit: tool.schema
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Maximum number of memory objects to return (default: 5)."
|
||||
),
|
||||
},
|
||||
async execute(args, toolCtx) {
|
||||
if (!gateway.healthy) {
|
||||
await gateway.checkHealth();
|
||||
if (!gateway.healthy) {
|
||||
return "Mnemosyne gateway is not available.";
|
||||
}
|
||||
}
|
||||
|
||||
const result = await gateway.queryMemory(
|
||||
toolCtx.sessionID,
|
||||
args.query,
|
||||
args.limit ?? 5
|
||||
);
|
||||
|
||||
if (!result || result.objects.length === 0) {
|
||||
return `No relevant memory objects found for query: "${args.query}"`;
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`Found ${result.objects.length} relevant objects (${result.total_objects} total in session, ${result.session_tokens.toLocaleString()} tokens stored):`,
|
||||
"",
|
||||
];
|
||||
|
||||
for (const obj of result.objects) {
|
||||
const fidelityLabel = ["L0:Full", "L1:Summary", "L2:Compact", "L3:Stub", "L4:Evicted"][obj.current_fidelity] ?? "Unknown";
|
||||
lines.push(
|
||||
`### ${obj.object_type} (${fidelityLabel}, similarity: ${(obj.similarity * 100).toFixed(1)}%)`
|
||||
);
|
||||
lines.push(obj.stub);
|
||||
lines.push(`Tokens: ${obj.tokens} | ID: ${obj.id}`);
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default MnemosynePlugin;
|
||||
20
opencode-plugin/tsconfig.json
Normal file
20
opencode-plugin/tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue