@xiaoxiamimengfb/my-opencode-mem 2.12.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.
- package/README.md +155 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +411 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +427 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +4 -0
- package/dist/services/ai/ai-provider-factory.d.ts +8 -0
- package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
- package/dist/services/ai/ai-provider-factory.js +28 -0
- package/dist/services/ai/opencode-provider.d.ts +30 -0
- package/dist/services/ai/opencode-provider.d.ts.map +1 -0
- package/dist/services/ai/opencode-provider.js +332 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.d.ts.map +1 -0
- package/dist/services/ai/provider-config.js +14 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +12 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +184 -0
- package/dist/services/ai/providers/base-provider.d.ts +25 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +23 -0
- package/dist/services/ai/providers/google-gemini.d.ts +16 -0
- package/dist/services/ai/providers/google-gemini.d.ts.map +1 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +13 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +277 -0
- package/dist/services/ai/providers/openai-responses.d.ts +14 -0
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
- package/dist/services/ai/session/ai-session-manager.js +166 -0
- package/dist/services/ai/session/session-types.d.ts +43 -0
- package/dist/services/ai/session/session-types.d.ts.map +1 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +41 -0
- package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +13 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
- package/dist/services/ai/validators/user-profile-validator.js +111 -0
- package/dist/services/api-handlers.d.ts +164 -0
- package/dist/services/api-handlers.d.ts.map +1 -0
- package/dist/services/api-handlers.js +901 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.d.ts.map +1 -0
- package/dist/services/auto-capture.js +306 -0
- package/dist/services/cleanup-service.d.ts +23 -0
- package/dist/services/cleanup-service.d.ts.map +1 -0
- package/dist/services/cleanup-service.js +102 -0
- package/dist/services/client.d.ts +118 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +251 -0
- package/dist/services/context.d.ts +11 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +24 -0
- package/dist/services/deduplication-service.d.ts +30 -0
- package/dist/services/deduplication-service.d.ts.map +1 -0
- package/dist/services/deduplication-service.js +124 -0
- package/dist/services/embedding.d.ts +15 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +106 -0
- package/dist/services/jsonc.d.ts +7 -0
- package/dist/services/jsonc.d.ts.map +1 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +3 -0
- package/dist/services/language-detector.d.ts.map +1 -0
- package/dist/services/language-detector.js +16 -0
- package/dist/services/logger.d.ts +2 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +51 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.d.ts.map +1 -0
- package/dist/services/migration-service.js +250 -0
- package/dist/services/privacy.d.ts +3 -0
- package/dist/services/privacy.d.ts.map +1 -0
- package/dist/services/privacy.js +7 -0
- package/dist/services/secret-resolver.d.ts +2 -0
- package/dist/services/secret-resolver.d.ts.map +1 -0
- package/dist/services/secret-resolver.js +55 -0
- package/dist/services/sqlite/connection-manager.d.ts +13 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
- package/dist/services/sqlite/connection-manager.js +74 -0
- package/dist/services/sqlite/shard-manager.d.ts +23 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
- package/dist/services/sqlite/shard-manager.js +288 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts +2 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
- package/dist/services/sqlite/sqlite-bootstrap.js +8 -0
- package/dist/services/sqlite/types.d.ts +42 -0
- package/dist/services/sqlite/types.d.ts.map +1 -0
- package/dist/services/sqlite/types.js +1 -0
- package/dist/services/sqlite/vector-search.d.ts +29 -0
- package/dist/services/sqlite/vector-search.d.ts.map +1 -0
- package/dist/services/sqlite/vector-search.js +268 -0
- package/dist/services/tags.d.ts +24 -0
- package/dist/services/tags.d.ts.map +1 -0
- package/dist/services/tags.js +146 -0
- package/dist/services/user-memory-learning.d.ts +3 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -0
- package/dist/services/user-memory-learning.js +231 -0
- package/dist/services/user-profile/profile-context.d.ts +2 -0
- package/dist/services/user-profile/profile-context.d.ts.map +1 -0
- package/dist/services/user-profile/profile-context.js +40 -0
- package/dist/services/user-profile/profile-utils.d.ts +3 -0
- package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +46 -0
- package/dist/services/user-profile/types.d.ts.map +1 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +23 -0
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
- package/dist/services/user-profile/user-profile-manager.js +292 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +41 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +192 -0
- package/dist/services/vector-backends/backend-factory.d.ts +3 -0
- package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
- package/dist/services/vector-backends/backend-factory.js +104 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/exact-scan-backend.js +63 -0
- package/dist/services/vector-backends/types.d.ts +51 -0
- package/dist/services/vector-backends/types.d.ts.map +1 -0
- package/dist/services/vector-backends/types.js +1 -0
- package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
- package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/usearch-backend.js +174 -0
- package/dist/services/web-server-worker.d.ts +2 -0
- package/dist/services/web-server-worker.d.ts.map +1 -0
- package/dist/services/web-server-worker.js +283 -0
- package/dist/services/web-server.d.ts +31 -0
- package/dist/services/web-server.d.ts.map +1 -0
- package/dist/services/web-server.js +356 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +1194 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/i18n.d.ts +2 -0
- package/dist/web/i18n.d.ts.map +1 -0
- package/dist/web/i18n.js +265 -0
- package/dist/web/index.html +284 -0
- package/dist/web/styles.css +1631 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# OpenCode Memory
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
A persistent memory system for AI coding agents that enables long-term context retention across sessions using local vector database technology.
|
|
10
|
+
|
|
11
|
+
## Visual Overview
|
|
12
|
+
|
|
13
|
+
**Project Memory Timeline:**
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
**User Profile Viewer:**
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Core Features
|
|
22
|
+
|
|
23
|
+
Local vector database with SQLite + USearch-first vector indexing and ExactScan fallback, persistent project memories, automatic user profile learning, unified memory-prompt timeline, full-featured web UI, intelligent prompt-based memory extraction, multi-provider AI support (OpenAI, Anthropic), 12+ local embedding models, smart deduplication, and built-in privacy protection.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
This plugin uses `USearch` for preferred in-memory vector indexing with automatic ExactScan fallback. No custom SQLite build or browser runtime shim is required.
|
|
28
|
+
|
|
29
|
+
**Recommended runtime:**
|
|
30
|
+
|
|
31
|
+
- Bun
|
|
32
|
+
- Standard OpenCode plugin environment
|
|
33
|
+
|
|
34
|
+
**Notes:**
|
|
35
|
+
|
|
36
|
+
- If `USearch` is unavailable or fails at runtime, the plugin automatically falls back to exact vector scanning.
|
|
37
|
+
- SQLite remains the source of truth; search indexes are rebuilt from SQLite data when needed.
|
|
38
|
+
|
|
39
|
+
## Getting Started
|
|
40
|
+
|
|
41
|
+
Add to your OpenCode configuration at `~/.config/opencode/opencode.json`:
|
|
42
|
+
|
|
43
|
+
```jsonc
|
|
44
|
+
{
|
|
45
|
+
"plugin": ["opencode-mem"],
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The plugin downloads automatically on next startup.
|
|
50
|
+
|
|
51
|
+
## Usage Examples
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
memory({ mode: "add", content: "Project uses microservices architecture" });
|
|
55
|
+
memory({ mode: "search", query: "architecture decisions" });
|
|
56
|
+
memory({ mode: "profile" });
|
|
57
|
+
memory({ mode: "list", limit: 10 });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Access the web interface at `http://127.0.0.1:4747` for visual memory browsing and management.
|
|
61
|
+
|
|
62
|
+
## Configuration Essentials
|
|
63
|
+
|
|
64
|
+
Configure at `~/.config/opencode/opencode-mem.jsonc`:
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
{
|
|
68
|
+
"storagePath": "~/.opencode-mem/data",
|
|
69
|
+
"userEmailOverride": "user@example.com",
|
|
70
|
+
"userNameOverride": "John Doe",
|
|
71
|
+
"embeddingModel": "Xenova/nomic-embed-text-v1",
|
|
72
|
+
"webServerEnabled": true,
|
|
73
|
+
"webServerPort": 4747,
|
|
74
|
+
|
|
75
|
+
"autoCaptureEnabled": true,
|
|
76
|
+
"autoCaptureLanguage": "auto",
|
|
77
|
+
|
|
78
|
+
"opencodeProvider": "anthropic",
|
|
79
|
+
"opencodeModel": "claude-haiku-4-5-20251001",
|
|
80
|
+
|
|
81
|
+
"showAutoCaptureToasts": true,
|
|
82
|
+
"showUserProfileToasts": true,
|
|
83
|
+
"showErrorToasts": true,
|
|
84
|
+
|
|
85
|
+
"userProfileAnalysisInterval": 10,
|
|
86
|
+
"maxMemories": 10,
|
|
87
|
+
|
|
88
|
+
"compaction": {
|
|
89
|
+
"enabled": true,
|
|
90
|
+
"memoryLimit": 10,
|
|
91
|
+
},
|
|
92
|
+
"chatMessage": {
|
|
93
|
+
"enabled": true,
|
|
94
|
+
"maxMemories": 3,
|
|
95
|
+
"excludeCurrentSession": true,
|
|
96
|
+
"maxAgeDays": undefined,
|
|
97
|
+
"injectOn": "first",
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Auto-Capture AI Provider
|
|
103
|
+
|
|
104
|
+
**Recommended:** Use opencode's built-in providers (no separate API key needed):
|
|
105
|
+
|
|
106
|
+
```jsonc
|
|
107
|
+
"opencodeProvider": "anthropic",
|
|
108
|
+
"opencodeModel": "claude-haiku-4-5-20251001",
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This leverages your existing opencode authentication (OAuth or API key). Works with Claude Pro/Max plans via OAuth - no individual API keys required.
|
|
112
|
+
|
|
113
|
+
Supported providers: `anthropic`, `openai`
|
|
114
|
+
|
|
115
|
+
**Fallback:** Manual API configuration (if not using opencodeProvider):
|
|
116
|
+
|
|
117
|
+
```jsonc
|
|
118
|
+
"memoryProvider": "openai-chat",
|
|
119
|
+
"memoryModel": "gpt-4o-mini",
|
|
120
|
+
"memoryApiUrl": "https://api.openai.com/v1",
|
|
121
|
+
"memoryApiKey": "sk-...",
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**API Key Formats:**
|
|
125
|
+
|
|
126
|
+
```jsonc
|
|
127
|
+
"memoryApiKey": "sk-..."
|
|
128
|
+
"memoryApiKey": "file://~/.config/opencode/api-key.txt"
|
|
129
|
+
"memoryApiKey": "env://OPENAI_API_KEY"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Full documentation available in this README.
|
|
133
|
+
|
|
134
|
+
## Development & Contribution
|
|
135
|
+
|
|
136
|
+
Build and test locally:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bun install
|
|
140
|
+
bun run build
|
|
141
|
+
bun run typecheck
|
|
142
|
+
bun run format
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This project is actively seeking contributions to become the definitive memory plugin for AI coding agents. Whether you are fixing bugs, adding features, improving documentation, or expanding embedding model support, your contributions are critical. The codebase is well-structured and ready for enhancement. If you hit a blocker or have improvement ideas, submit a pull request - we review and merge contributions quickly.
|
|
146
|
+
|
|
147
|
+
## License & Links
|
|
148
|
+
|
|
149
|
+
MIT License - see LICENSE file
|
|
150
|
+
|
|
151
|
+
- **Repository**: https://github.com/tickernelz/opencode-mem
|
|
152
|
+
- **Issues**: https://github.com/tickernelz/opencode-mem/issues
|
|
153
|
+
- **OpenCode Platform**: https://opencode.ai
|
|
154
|
+
|
|
155
|
+
Inspired by [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory)
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const CONFIG: {
|
|
2
|
+
storagePath: string;
|
|
3
|
+
userEmailOverride: string | undefined;
|
|
4
|
+
userNameOverride: string | undefined;
|
|
5
|
+
embeddingModel: string;
|
|
6
|
+
embeddingDimensions: number;
|
|
7
|
+
embeddingApiUrl: string | undefined;
|
|
8
|
+
embeddingApiKey: string | undefined;
|
|
9
|
+
similarityThreshold: number;
|
|
10
|
+
maxMemories: number;
|
|
11
|
+
maxProfileItems: number;
|
|
12
|
+
injectProfile: boolean;
|
|
13
|
+
containerTagPrefix: string;
|
|
14
|
+
autoCaptureEnabled: boolean;
|
|
15
|
+
autoCaptureMaxIterations: number;
|
|
16
|
+
autoCaptureIterationTimeout: number;
|
|
17
|
+
autoCaptureLanguage: string | undefined;
|
|
18
|
+
memoryProvider: "openai-chat" | "openai-responses" | "anthropic";
|
|
19
|
+
memoryModel: string | undefined;
|
|
20
|
+
memoryApiUrl: string | undefined;
|
|
21
|
+
memoryApiKey: string | undefined;
|
|
22
|
+
memoryTemperature: number | false | undefined;
|
|
23
|
+
memoryExtraParams: Record<string, unknown> | undefined;
|
|
24
|
+
opencodeProvider: string | undefined;
|
|
25
|
+
opencodeModel: string | undefined;
|
|
26
|
+
vectorBackend: "usearch-first" | "usearch" | "exact-scan";
|
|
27
|
+
aiSessionRetentionDays: number;
|
|
28
|
+
webServerEnabled: boolean;
|
|
29
|
+
webServerPort: number;
|
|
30
|
+
webServerHost: string;
|
|
31
|
+
maxVectorsPerShard: number;
|
|
32
|
+
autoCleanupEnabled: boolean;
|
|
33
|
+
autoCleanupRetentionDays: number;
|
|
34
|
+
deduplicationEnabled: boolean;
|
|
35
|
+
deduplicationSimilarityThreshold: number;
|
|
36
|
+
userProfileAnalysisInterval: number;
|
|
37
|
+
userProfileMaxPreferences: number;
|
|
38
|
+
userProfileMaxPatterns: number;
|
|
39
|
+
userProfileMaxWorkflows: number;
|
|
40
|
+
userProfileConfidenceDecayDays: number;
|
|
41
|
+
userProfileChangelogRetentionCount: number;
|
|
42
|
+
showAutoCaptureToasts: boolean;
|
|
43
|
+
showUserProfileToasts: boolean;
|
|
44
|
+
showErrorToasts: boolean;
|
|
45
|
+
compaction: {
|
|
46
|
+
enabled: boolean | undefined;
|
|
47
|
+
memoryLimit: number | undefined;
|
|
48
|
+
};
|
|
49
|
+
chatMessage: {
|
|
50
|
+
enabled: boolean | undefined;
|
|
51
|
+
maxMemories: number | undefined;
|
|
52
|
+
excludeCurrentSession: boolean | undefined;
|
|
53
|
+
maxAgeDays: number | undefined;
|
|
54
|
+
injectOn: "first" | "always";
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export declare function isConfigured(): boolean;
|
|
58
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAqcA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;oBAwBb,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;mBASX,eAAe,GACf,SAAS,GACT,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoCV,OAAO,GACP,QAAQ;;CAEf,CAAC;AAEF,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { stripJsoncComments } from "./services/jsonc.js";
|
|
5
|
+
import { resolveSecretValue } from "./services/secret-resolver.js";
|
|
6
|
+
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
7
|
+
const DATA_DIR = join(homedir(), ".opencode-mem");
|
|
8
|
+
const CONFIG_FILES = [
|
|
9
|
+
join(CONFIG_DIR, "opencode-mem.jsonc"),
|
|
10
|
+
join(CONFIG_DIR, "opencode-mem.json"),
|
|
11
|
+
];
|
|
12
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
13
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
if (!existsSync(DATA_DIR)) {
|
|
16
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
storagePath: join(DATA_DIR, "data"),
|
|
20
|
+
embeddingModel: "Xenova/nomic-embed-text-v1",
|
|
21
|
+
embeddingDimensions: 768,
|
|
22
|
+
similarityThreshold: 0.6,
|
|
23
|
+
maxMemories: 10,
|
|
24
|
+
maxProfileItems: 5,
|
|
25
|
+
injectProfile: true,
|
|
26
|
+
containerTagPrefix: "opencode",
|
|
27
|
+
autoCaptureEnabled: true,
|
|
28
|
+
autoCaptureMaxIterations: 5,
|
|
29
|
+
autoCaptureIterationTimeout: 30000,
|
|
30
|
+
vectorBackend: "usearch-first",
|
|
31
|
+
aiSessionRetentionDays: 7,
|
|
32
|
+
webServerEnabled: true,
|
|
33
|
+
webServerPort: 4747,
|
|
34
|
+
webServerHost: "127.0.0.1",
|
|
35
|
+
maxVectorsPerShard: 50000,
|
|
36
|
+
autoCleanupEnabled: true,
|
|
37
|
+
autoCleanupRetentionDays: 30,
|
|
38
|
+
deduplicationEnabled: true,
|
|
39
|
+
deduplicationSimilarityThreshold: 0.9,
|
|
40
|
+
userProfileAnalysisInterval: 10,
|
|
41
|
+
userProfileMaxPreferences: 20,
|
|
42
|
+
userProfileMaxPatterns: 15,
|
|
43
|
+
userProfileMaxWorkflows: 10,
|
|
44
|
+
userProfileConfidenceDecayDays: 30,
|
|
45
|
+
userProfileChangelogRetentionCount: 5,
|
|
46
|
+
showAutoCaptureToasts: true,
|
|
47
|
+
showUserProfileToasts: true,
|
|
48
|
+
showErrorToasts: true,
|
|
49
|
+
compaction: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
memoryLimit: 10,
|
|
52
|
+
},
|
|
53
|
+
chatMessage: {
|
|
54
|
+
enabled: true,
|
|
55
|
+
maxMemories: 3,
|
|
56
|
+
excludeCurrentSession: true,
|
|
57
|
+
maxAgeDays: undefined,
|
|
58
|
+
injectOn: "first",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
function expandPath(path) {
|
|
62
|
+
if (path.startsWith("~/")) {
|
|
63
|
+
return join(homedir(), path.slice(2));
|
|
64
|
+
}
|
|
65
|
+
if (path === "~") {
|
|
66
|
+
return homedir();
|
|
67
|
+
}
|
|
68
|
+
return path;
|
|
69
|
+
}
|
|
70
|
+
function loadConfig() {
|
|
71
|
+
for (const path of CONFIG_FILES) {
|
|
72
|
+
if (existsSync(path)) {
|
|
73
|
+
try {
|
|
74
|
+
const content = readFileSync(path, "utf-8");
|
|
75
|
+
const json = stripJsoncComments(content);
|
|
76
|
+
return JSON.parse(json);
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const fileConfig = loadConfig();
|
|
84
|
+
const CONFIG_TEMPLATE = `{
|
|
85
|
+
// ============================================
|
|
86
|
+
// OpenCode Memory Plugin Configuration
|
|
87
|
+
// ============================================
|
|
88
|
+
|
|
89
|
+
// Storage location for vector database
|
|
90
|
+
"storagePath": "~/.opencode-mem/data",
|
|
91
|
+
|
|
92
|
+
"userEmailOverride": "",
|
|
93
|
+
"userNameOverride": "",
|
|
94
|
+
|
|
95
|
+
// ============================================
|
|
96
|
+
// Embedding Model (for similarity search)
|
|
97
|
+
// ============================================
|
|
98
|
+
|
|
99
|
+
// Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)
|
|
100
|
+
"embeddingModel": "Xenova/nomic-embed-text-v1",
|
|
101
|
+
|
|
102
|
+
// Auto-detected dimensions (no need to set manually)
|
|
103
|
+
// "embeddingDimensions": 768,
|
|
104
|
+
|
|
105
|
+
// Other recommended models:
|
|
106
|
+
// "embeddingModel": "Xenova/jina-embeddings-v2-base-en", // 768 dims, English-only, 8192 context
|
|
107
|
+
// "embeddingModel": "Xenova/jina-embeddings-v2-small-en", // 512 dims, faster, 8192 context
|
|
108
|
+
// "embeddingModel": "Xenova/all-MiniLM-L6-v2", // 384 dims, very fast, 512 context
|
|
109
|
+
// "embeddingModel": "Xenova/all-mpnet-base-v2", // 768 dims, good quality, 512 context
|
|
110
|
+
|
|
111
|
+
// Optional: Use OpenAI-compatible API for embeddings
|
|
112
|
+
// "embeddingApiUrl": "https://api.openai.com/v1",
|
|
113
|
+
// "embeddingApiKey": "sk-...",
|
|
114
|
+
// "embeddingModel": "text-embedding-3-small", // 1536 dims, auto-detected
|
|
115
|
+
|
|
116
|
+
// ============================================
|
|
117
|
+
// Web Server Settings
|
|
118
|
+
// ============================================
|
|
119
|
+
|
|
120
|
+
// Enable web UI for managing memories (accessible at http://localhost:4747)
|
|
121
|
+
"webServerEnabled": true,
|
|
122
|
+
|
|
123
|
+
// Port for web UI server
|
|
124
|
+
"webServerPort": 4747,
|
|
125
|
+
|
|
126
|
+
// Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)
|
|
127
|
+
"webServerHost": "127.0.0.1",
|
|
128
|
+
|
|
129
|
+
// ============================================
|
|
130
|
+
// Database Settings
|
|
131
|
+
// ============================================
|
|
132
|
+
|
|
133
|
+
// Maximum vectors per database shard (auto-creates new shard when limit reached)
|
|
134
|
+
"maxVectorsPerShard": 50000,
|
|
135
|
+
|
|
136
|
+
// Automatically delete old memories based on retention period
|
|
137
|
+
"autoCleanupEnabled": true,
|
|
138
|
+
|
|
139
|
+
// Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)
|
|
140
|
+
"autoCleanupRetentionDays": 30,
|
|
141
|
+
|
|
142
|
+
// Automatically detect and remove duplicate memories
|
|
143
|
+
"deduplicationEnabled": true,
|
|
144
|
+
|
|
145
|
+
// Similarity threshold (0-1) for detecting duplicates (higher = stricter)
|
|
146
|
+
"deduplicationSimilarityThreshold": 0.90,
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// OpenCode Provider Settings (RECOMMENDED)
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
// Use opencode's already-configured providers for auto-capture and user profile learning.
|
|
153
|
+
// When set, no separate API key is needed — uses your existing opencode authentication
|
|
154
|
+
// (including Claude Pro/Max plans via OAuth, or any API key configured in opencode).
|
|
155
|
+
//
|
|
156
|
+
// If NOT set, falls back to the manual config (memoryApiKey/memoryApiUrl/memoryModel below).
|
|
157
|
+
//
|
|
158
|
+
// Examples:
|
|
159
|
+
// Anthropic (OAuth/API key): "opencodeProvider": "anthropic", "opencodeModel": "claude-haiku-4-5-20251001"
|
|
160
|
+
// OpenAI (API key): "opencodeProvider": "openai", "opencodeModel": "gpt-4o-mini"
|
|
161
|
+
//
|
|
162
|
+
// The provider name must match a connected provider in opencode (check with: opencode providers list)
|
|
163
|
+
// "opencodeProvider": "anthropic",
|
|
164
|
+
// "opencodeModel": "claude-haiku-4-5-20251001",
|
|
165
|
+
|
|
166
|
+
// ============================================
|
|
167
|
+
// Auto-Capture Settings (REQUIRES EXTERNAL API)
|
|
168
|
+
// ============================================
|
|
169
|
+
|
|
170
|
+
// IMPORTANT: Auto-capture ONLY works with external API
|
|
171
|
+
// It runs in background without blocking your main session
|
|
172
|
+
// Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.
|
|
173
|
+
|
|
174
|
+
"autoCaptureEnabled": true,
|
|
175
|
+
|
|
176
|
+
// Provider type: "openai-chat" | "openai-responses" | "anthropic"
|
|
177
|
+
"memoryProvider": "openai-chat",
|
|
178
|
+
|
|
179
|
+
// REQUIRED for auto-capture (all 3 must be set):
|
|
180
|
+
"memoryModel": "gpt-4o-mini",
|
|
181
|
+
"memoryApiUrl": "https://api.openai.com/v1",
|
|
182
|
+
"memoryApiKey": "sk-...",
|
|
183
|
+
|
|
184
|
+
// API Key Formats:
|
|
185
|
+
// Direct value: "sk-..."
|
|
186
|
+
// From file: "file://~/.config/litellm-key.txt"
|
|
187
|
+
// From env variable: "env://LITELLM_API_KEY"
|
|
188
|
+
|
|
189
|
+
// Examples for different providers:
|
|
190
|
+
// OpenAI Chat Completion (default, backward compatible):
|
|
191
|
+
// "memoryProvider": "openai-chat"
|
|
192
|
+
// "memoryModel": "gpt-4o-mini"
|
|
193
|
+
// "memoryApiUrl": "https://api.openai.com/v1"
|
|
194
|
+
// "memoryApiKey": "sk-..."
|
|
195
|
+
|
|
196
|
+
// OpenAI Responses API (recommended, with session support):
|
|
197
|
+
// "memoryProvider": "openai-responses"
|
|
198
|
+
// "memoryModel": "gpt-4o"
|
|
199
|
+
// "memoryApiUrl": "https://api.openai.com/v1"
|
|
200
|
+
// "memoryApiKey": "sk-..."
|
|
201
|
+
|
|
202
|
+
// Anthropic (with session support):
|
|
203
|
+
// "memoryProvider": "anthropic"
|
|
204
|
+
// "memoryModel": "claude-3-5-haiku-20241022"
|
|
205
|
+
// "memoryApiUrl": "https://api.anthropic.com/v1"
|
|
206
|
+
// "memoryApiKey": "sk-ant-..."
|
|
207
|
+
|
|
208
|
+
// Groq (OpenAI-compatible, use openai-chat provider):
|
|
209
|
+
// "memoryProvider": "openai-chat"
|
|
210
|
+
// "memoryModel": "llama-3.3-70b-versatile"
|
|
211
|
+
// "memoryApiUrl": "https://api.groq.com/openai/v1"
|
|
212
|
+
// "memoryApiKey": "gsk_..."
|
|
213
|
+
|
|
214
|
+
// Maximum iterations for multi-turn AI analysis (for openai-responses and anthropic)
|
|
215
|
+
"autoCaptureMaxIterations": 5,
|
|
216
|
+
|
|
217
|
+
// Timeout per iteration in milliseconds (30 seconds default)
|
|
218
|
+
"autoCaptureIterationTimeout": 30000,
|
|
219
|
+
|
|
220
|
+
// Days to keep AI session history before cleanup
|
|
221
|
+
"aiSessionRetentionDays": 7,
|
|
222
|
+
|
|
223
|
+
// Temperature for AI API requests (set to false to omit parameter for models that don't support it)
|
|
224
|
+
// Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter
|
|
225
|
+
// Set to false and add "memoryTemperature": false in config when using such models
|
|
226
|
+
"memoryTemperature": 0.3,
|
|
227
|
+
|
|
228
|
+
// Extra parameters to include in API request body
|
|
229
|
+
// Useful for local inference servers (e.g. llama-server with --jinja) that support
|
|
230
|
+
// additional parameters like disabling thinking/reasoning mode
|
|
231
|
+
// Example for Qwen3 models: { "enable_thinking": false }
|
|
232
|
+
// "memoryExtraParams": {},
|
|
233
|
+
|
|
234
|
+
// Language for auto-capture summaries (default: "auto" for auto-detection)
|
|
235
|
+
// Options: "auto", "en", "id", "zh", "ja", "es", "fr", "de", "ru", "pt", "ar", "ko"
|
|
236
|
+
// "autoCaptureLanguage": "auto",
|
|
237
|
+
|
|
238
|
+
// ============================================
|
|
239
|
+
// Toast Notifications
|
|
240
|
+
// ============================================
|
|
241
|
+
|
|
242
|
+
// Show toast when memory is auto-captured
|
|
243
|
+
"showAutoCaptureToasts": true,
|
|
244
|
+
|
|
245
|
+
// Show toast when user profile is updated
|
|
246
|
+
"showUserProfileToasts": true,
|
|
247
|
+
|
|
248
|
+
// Show toast for error messages
|
|
249
|
+
"showErrorToasts": true,
|
|
250
|
+
|
|
251
|
+
// ============================================
|
|
252
|
+
// User Profile System
|
|
253
|
+
// ============================================
|
|
254
|
+
|
|
255
|
+
// Analyze user prompts every N prompts to build/update your user profile
|
|
256
|
+
// When N uncaptured prompts accumulate, AI will analyze them to identify:
|
|
257
|
+
// - User preferences (code style, communication style, tool preferences)
|
|
258
|
+
// - User patterns (recurring topics, problem domains, technical interests)
|
|
259
|
+
// - User workflows (development habits, sequences, learning style)
|
|
260
|
+
// - Skill level (overall and per-domain assessment)
|
|
261
|
+
"userProfileAnalysisInterval": 10,
|
|
262
|
+
|
|
263
|
+
// Maximum number of preferences to keep in user profile (sorted by confidence)
|
|
264
|
+
// Preferences are things like "prefers code without comments", "likes concise responses"
|
|
265
|
+
"userProfileMaxPreferences": 20,
|
|
266
|
+
|
|
267
|
+
// Maximum number of patterns to keep in user profile (sorted by frequency)
|
|
268
|
+
// Patterns are recurring topics like "often asks about database optimization"
|
|
269
|
+
"userProfileMaxPatterns": 15,
|
|
270
|
+
|
|
271
|
+
// Maximum number of workflows to keep in user profile (sorted by frequency)
|
|
272
|
+
// Workflows are sequences like "usually asks for tests after implementation"
|
|
273
|
+
"userProfileMaxWorkflows": 10,
|
|
274
|
+
|
|
275
|
+
// Days before preference confidence starts to decay (if not reinforced)
|
|
276
|
+
// Preferences that aren't seen again will gradually lose confidence and be removed
|
|
277
|
+
"userProfileConfidenceDecayDays": 30,
|
|
278
|
+
|
|
279
|
+
// Number of profile versions to keep in changelog (for rollback/debugging)
|
|
280
|
+
// Older versions are automatically cleaned up
|
|
281
|
+
"userProfileChangelogRetentionCount": 5,
|
|
282
|
+
|
|
283
|
+
// ============================================
|
|
284
|
+
// Search Settings
|
|
285
|
+
// ============================================
|
|
286
|
+
|
|
287
|
+
// Minimum similarity score (0-1) for memory search results
|
|
288
|
+
"similarityThreshold": 0.6,
|
|
289
|
+
|
|
290
|
+
// Maximum number of memories to return in search results
|
|
291
|
+
"maxMemories": 10,
|
|
292
|
+
|
|
293
|
+
// ============================================
|
|
294
|
+
// Advanced Settings
|
|
295
|
+
// ============================================
|
|
296
|
+
|
|
297
|
+
// Inject user profile into AI context (preferences, patterns, workflows)
|
|
298
|
+
"injectProfile": true
|
|
299
|
+
}
|
|
300
|
+
`;
|
|
301
|
+
function ensureConfigExists() {
|
|
302
|
+
const configPath = join(CONFIG_DIR, "opencode-mem.jsonc");
|
|
303
|
+
if (!existsSync(configPath)) {
|
|
304
|
+
try {
|
|
305
|
+
writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
|
|
306
|
+
console.log(`\n✓ Created config template: ${configPath}`);
|
|
307
|
+
console.log(" Edit this file to customize opencode-mem settings.\n");
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
ensureConfigExists();
|
|
313
|
+
function getEmbeddingDimensions(model) {
|
|
314
|
+
const dimensionMap = {
|
|
315
|
+
// Local Xenova models
|
|
316
|
+
"Xenova/nomic-embed-text-v1": 768,
|
|
317
|
+
"Xenova/nomic-embed-text-v1-unsupervised": 768,
|
|
318
|
+
"Xenova/nomic-embed-text-v1-ablated": 768,
|
|
319
|
+
"Xenova/jina-embeddings-v2-base-en": 768,
|
|
320
|
+
"Xenova/jina-embeddings-v2-base-zh": 768,
|
|
321
|
+
"Xenova/jina-embeddings-v2-base-de": 768,
|
|
322
|
+
"Xenova/jina-embeddings-v2-small-en": 512,
|
|
323
|
+
"Xenova/all-MiniLM-L6-v2": 384,
|
|
324
|
+
"Xenova/all-MiniLM-L12-v2": 384,
|
|
325
|
+
"Xenova/all-mpnet-base-v2": 768,
|
|
326
|
+
"Xenova/bge-base-en-v1.5": 768,
|
|
327
|
+
"Xenova/bge-small-en-v1.5": 384,
|
|
328
|
+
"Xenova/gte-small": 384,
|
|
329
|
+
"Xenova/GIST-small-Embedding-v0": 384,
|
|
330
|
+
"Xenova/text-embedding-ada-002": 1536,
|
|
331
|
+
// OpenAI API models
|
|
332
|
+
"text-embedding-3-small": 1536,
|
|
333
|
+
"text-embedding-3-large": 3072,
|
|
334
|
+
"text-embedding-ada-002": 1536,
|
|
335
|
+
// Cohere API models
|
|
336
|
+
"embed-english-v3.0": 1024,
|
|
337
|
+
"embed-multilingual-v3.0": 1024,
|
|
338
|
+
"embed-english-light-v3.0": 384,
|
|
339
|
+
"embed-multilingual-light-v3.0": 384,
|
|
340
|
+
// Google API models
|
|
341
|
+
"text-embedding-004": 768,
|
|
342
|
+
"text-multilingual-embedding-002": 768,
|
|
343
|
+
// Voyage AI models
|
|
344
|
+
"voyage-3": 1024,
|
|
345
|
+
"voyage-3-lite": 512,
|
|
346
|
+
"voyage-code-3": 1024,
|
|
347
|
+
};
|
|
348
|
+
return dimensionMap[model] || 768;
|
|
349
|
+
}
|
|
350
|
+
export const CONFIG = {
|
|
351
|
+
storagePath: expandPath(fileConfig.storagePath ?? DEFAULTS.storagePath),
|
|
352
|
+
userEmailOverride: fileConfig.userEmailOverride,
|
|
353
|
+
userNameOverride: fileConfig.userNameOverride,
|
|
354
|
+
embeddingModel: fileConfig.embeddingModel ?? DEFAULTS.embeddingModel,
|
|
355
|
+
embeddingDimensions: fileConfig.embeddingDimensions ??
|
|
356
|
+
getEmbeddingDimensions(fileConfig.embeddingModel ?? DEFAULTS.embeddingModel),
|
|
357
|
+
embeddingApiUrl: fileConfig.embeddingApiUrl,
|
|
358
|
+
embeddingApiKey: fileConfig.embeddingApiUrl
|
|
359
|
+
? resolveSecretValue(fileConfig.embeddingApiKey ?? process.env.OPENAI_API_KEY)
|
|
360
|
+
: undefined,
|
|
361
|
+
similarityThreshold: fileConfig.similarityThreshold ?? DEFAULTS.similarityThreshold,
|
|
362
|
+
maxMemories: fileConfig.maxMemories ?? DEFAULTS.maxMemories,
|
|
363
|
+
maxProfileItems: fileConfig.maxProfileItems ?? DEFAULTS.maxProfileItems,
|
|
364
|
+
injectProfile: fileConfig.injectProfile ?? DEFAULTS.injectProfile,
|
|
365
|
+
containerTagPrefix: fileConfig.containerTagPrefix ?? DEFAULTS.containerTagPrefix,
|
|
366
|
+
autoCaptureEnabled: fileConfig.autoCaptureEnabled ?? DEFAULTS.autoCaptureEnabled,
|
|
367
|
+
autoCaptureMaxIterations: fileConfig.autoCaptureMaxIterations ?? DEFAULTS.autoCaptureMaxIterations,
|
|
368
|
+
autoCaptureIterationTimeout: fileConfig.autoCaptureIterationTimeout ?? DEFAULTS.autoCaptureIterationTimeout,
|
|
369
|
+
autoCaptureLanguage: fileConfig.autoCaptureLanguage,
|
|
370
|
+
memoryProvider: (fileConfig.memoryProvider ?? "openai-chat"),
|
|
371
|
+
memoryModel: fileConfig.memoryModel,
|
|
372
|
+
memoryApiUrl: fileConfig.memoryApiUrl,
|
|
373
|
+
memoryApiKey: resolveSecretValue(fileConfig.memoryApiKey),
|
|
374
|
+
memoryTemperature: fileConfig.memoryTemperature,
|
|
375
|
+
memoryExtraParams: fileConfig.memoryExtraParams,
|
|
376
|
+
opencodeProvider: fileConfig.opencodeProvider,
|
|
377
|
+
opencodeModel: fileConfig.opencodeModel,
|
|
378
|
+
vectorBackend: (fileConfig.vectorBackend ?? "usearch-first"),
|
|
379
|
+
aiSessionRetentionDays: fileConfig.aiSessionRetentionDays ?? DEFAULTS.aiSessionRetentionDays,
|
|
380
|
+
webServerEnabled: fileConfig.webServerEnabled ?? DEFAULTS.webServerEnabled,
|
|
381
|
+
webServerPort: fileConfig.webServerPort ?? DEFAULTS.webServerPort,
|
|
382
|
+
webServerHost: fileConfig.webServerHost ?? DEFAULTS.webServerHost,
|
|
383
|
+
maxVectorsPerShard: fileConfig.maxVectorsPerShard ?? DEFAULTS.maxVectorsPerShard,
|
|
384
|
+
autoCleanupEnabled: fileConfig.autoCleanupEnabled ?? DEFAULTS.autoCleanupEnabled,
|
|
385
|
+
autoCleanupRetentionDays: fileConfig.autoCleanupRetentionDays ?? DEFAULTS.autoCleanupRetentionDays,
|
|
386
|
+
deduplicationEnabled: fileConfig.deduplicationEnabled ?? DEFAULTS.deduplicationEnabled,
|
|
387
|
+
deduplicationSimilarityThreshold: fileConfig.deduplicationSimilarityThreshold ?? DEFAULTS.deduplicationSimilarityThreshold,
|
|
388
|
+
userProfileAnalysisInterval: fileConfig.userProfileAnalysisInterval ?? DEFAULTS.userProfileAnalysisInterval,
|
|
389
|
+
userProfileMaxPreferences: fileConfig.userProfileMaxPreferences ?? DEFAULTS.userProfileMaxPreferences,
|
|
390
|
+
userProfileMaxPatterns: fileConfig.userProfileMaxPatterns ?? DEFAULTS.userProfileMaxPatterns,
|
|
391
|
+
userProfileMaxWorkflows: fileConfig.userProfileMaxWorkflows ?? DEFAULTS.userProfileMaxWorkflows,
|
|
392
|
+
userProfileConfidenceDecayDays: fileConfig.userProfileConfidenceDecayDays ?? DEFAULTS.userProfileConfidenceDecayDays,
|
|
393
|
+
userProfileChangelogRetentionCount: fileConfig.userProfileChangelogRetentionCount ?? DEFAULTS.userProfileChangelogRetentionCount,
|
|
394
|
+
showAutoCaptureToasts: fileConfig.showAutoCaptureToasts ?? DEFAULTS.showAutoCaptureToasts,
|
|
395
|
+
showUserProfileToasts: fileConfig.showUserProfileToasts ?? DEFAULTS.showUserProfileToasts,
|
|
396
|
+
showErrorToasts: fileConfig.showErrorToasts ?? DEFAULTS.showErrorToasts,
|
|
397
|
+
compaction: {
|
|
398
|
+
enabled: fileConfig.compaction?.enabled ?? DEFAULTS.compaction.enabled,
|
|
399
|
+
memoryLimit: fileConfig.compaction?.memoryLimit ?? DEFAULTS.compaction.memoryLimit,
|
|
400
|
+
},
|
|
401
|
+
chatMessage: {
|
|
402
|
+
enabled: fileConfig.chatMessage?.enabled ?? DEFAULTS.chatMessage.enabled,
|
|
403
|
+
maxMemories: fileConfig.chatMessage?.maxMemories ?? DEFAULTS.chatMessage.maxMemories,
|
|
404
|
+
excludeCurrentSession: fileConfig.chatMessage?.excludeCurrentSession ?? DEFAULTS.chatMessage.excludeCurrentSession,
|
|
405
|
+
maxAgeDays: fileConfig.chatMessage?.maxAgeDays,
|
|
406
|
+
injectOn: (fileConfig.chatMessage?.injectOn ?? DEFAULTS.chatMessage.injectOn),
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
export function isConfigured() {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAmB/D,eAAO,MAAM,iBAAiB,EAAE,MA+b/B,CAAC"}
|