toon-memory 1.0.8 โ 1.1.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/LICENSE +21 -0
- package/README.md +220 -0
- package/bin/setup.js +191 -90
- package/install.ps1 +62 -0
- package/install.sh +75 -0
- package/mcp/server.js +7 -7
- package/package.json +22 -3
- package/src/mcp/server.ts +1 -1
- package/uninstall.sh +107 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luis Angel Valencia Valera
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# toon-memory
|
|
2
|
+
|
|
3
|
+
Persistent memory for AI coding agents โ remember decisions, patterns, and bugs between sessions.
|
|
4
|
+
|
|
5
|
+
๐ **1.0 Released!**
|
|
6
|
+
|
|
7
|
+
Supercharge **OpenCode, VS Code Copilot, Claude Code, Cursor, Windsurf, Cline, Continue** and more with Persistent Memory
|
|
8
|
+
|
|
9
|
+
Saves context in **TOON format** (40% fewer tokens than JSON) ยท 5 MCP tools ยท Works everywhere
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/toon-memory)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What is toon-memory?
|
|
17
|
+
|
|
18
|
+
AI agents forget everything between sessions. toon-memory fixes this by providing persistent memory that survives restarts.
|
|
19
|
+
|
|
20
|
+
**Use cases:**
|
|
21
|
+
- Remember design decisions ("Why did we choose X over Y?")
|
|
22
|
+
- Track patterns ("This project uses Zod for validation")
|
|
23
|
+
- Store bug resolutions ("Redis pool exhaustion fix")
|
|
24
|
+
- Save project knowledge ("Broker uses RESP protocol")
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Get Started
|
|
29
|
+
|
|
30
|
+
### 1. Install (one command)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# macOS / Linux
|
|
34
|
+
curl -fsSL https://raw.githubusercontent.com/LuiggiVal08/toon-memory/main/install.sh | sh
|
|
35
|
+
|
|
36
|
+
# Windows (PowerShell)
|
|
37
|
+
irm https://raw.githubusercontent.com/LuiggiVal08/toon-memory/main/install.ps1 | iex
|
|
38
|
+
|
|
39
|
+
# Or with npm (any platform)
|
|
40
|
+
npm i -g toon-memory
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Configure your agent(s)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Interactive installer โ detects agents and configures MCP
|
|
47
|
+
npx toon-memory
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Use it
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# In your next session, run:
|
|
54
|
+
memory_stats # See what's in memory
|
|
55
|
+
memory_recall # Search memory before reading files
|
|
56
|
+
memory_remember # Save important decisions
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Supported Agents
|
|
62
|
+
|
|
63
|
+
| Agent | Config Location | Status |
|
|
64
|
+
|-------|-----------------|--------|
|
|
65
|
+
| **OpenCode** | `.opencode/opencode.json` | โ
Full support |
|
|
66
|
+
| **VS Code / Copilot** | `.vscode/mcp.json` | โ
Full support |
|
|
67
|
+
| **Claude Code** | `.claude/settings.json` | โ
Full support |
|
|
68
|
+
| **Cursor** | `.cursor/mcp.json` | โ
Full support |
|
|
69
|
+
| **Windsurf** | `.windsurfrules` | โ
Full support |
|
|
70
|
+
| **Cline** | `.cline/mcp.json` | โ
Full support |
|
|
71
|
+
| **Continue** | `.continue/config.json` | โ
Full support |
|
|
72
|
+
| **Aider** | `.aider.conf.yml` | โ ๏ธ Manual config |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Tools
|
|
77
|
+
|
|
78
|
+
| Tool | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `memory_remember` | Save a decision, pattern, bug, or knowledge |
|
|
81
|
+
| `memory_recall` | Search memory (use BEFORE reading files) |
|
|
82
|
+
| `memory_forget` | Remove an entry by key or id |
|
|
83
|
+
| `memory_stats` | View memory state |
|
|
84
|
+
| `memory_summary` | Save/retrieve file summaries |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## How It Works
|
|
89
|
+
|
|
90
|
+
1. **MCP Server** โ runs locally, talks to your agent via stdio
|
|
91
|
+
2. **TOON Format** โ stores data in Token-Oriented Object Notation (~40% fewer tokens than JSON)
|
|
92
|
+
3. **Per-project memory** โ each project gets `.opencode/memory/data.toon`
|
|
93
|
+
4. **Zero config** โ just install and use
|
|
94
|
+
|
|
95
|
+
### Memory File Format
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
version: 1
|
|
99
|
+
entries[3|]{id|category|key|content|file|tags|date}:
|
|
100
|
+
a1b2c3d4|decision|use-zod|Use Zod for validation|src/types.ts|validation;types|2026-07-10
|
|
101
|
+
e5f6g7h8|pattern|pydantic-configs|Project uses Pydantic v2|config.py|python;patterns|2026-07-10
|
|
102
|
+
i9j0k1l2|bug|redis-pool-fix|Added max_connections=20|redis.ts|redis;fix|2026-07-10
|
|
103
|
+
summaries:
|
|
104
|
+
src/services/redis.ts: Redis connection pool with retry logic
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## CLI Commands
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx toon-memory # Interactive installer
|
|
113
|
+
npx toon-memory init # Quick setup (no prompts)
|
|
114
|
+
npx toon-memory mcp # Run MCP server directly
|
|
115
|
+
npx toon-memory status # Check installation status
|
|
116
|
+
npx toon-memory upgrade # Update to latest version
|
|
117
|
+
npx toon-memory uninstall # Remove from all agents
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Uninstall
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Remove from all agents and uninstall CLI
|
|
126
|
+
npx toon-memory uninstall
|
|
127
|
+
|
|
128
|
+
# Or with the global install
|
|
129
|
+
toon-memory uninstall
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
### Global (all projects)
|
|
137
|
+
|
|
138
|
+
Add to `~/.config/opencode/opencode.json`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mcp": {
|
|
143
|
+
"toon-memory": {
|
|
144
|
+
"type": "local",
|
|
145
|
+
"command": ["npx", "-y", "toon-memory", "mcp"],
|
|
146
|
+
"enabled": true
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Project-level
|
|
153
|
+
|
|
154
|
+
Add to `.opencode/opencode.json`:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"mcp": {
|
|
159
|
+
"toon-memory": {
|
|
160
|
+
"type": "local",
|
|
161
|
+
"command": ["npx", "-y", "toon-memory", "mcp"],
|
|
162
|
+
"enabled": true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### VS Code
|
|
169
|
+
|
|
170
|
+
Add to `.vscode/mcp.json`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"servers": {
|
|
175
|
+
"toon-memory": {
|
|
176
|
+
"command": "npx",
|
|
177
|
+
"args": ["-y", "toon-memory", "mcp"]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Why TOON?
|
|
186
|
+
|
|
187
|
+
TOON (Token-Oriented Object Notation) is designed for LLMs:
|
|
188
|
+
|
|
189
|
+
| Format | Token Count | LLM Accuracy |
|
|
190
|
+
|--------|-------------|--------------|
|
|
191
|
+
| JSON | 100 | 75% |
|
|
192
|
+
| YAML | 95 | 72% |
|
|
193
|
+
| **TOON** | **60** | **76.4%** |
|
|
194
|
+
|
|
195
|
+
- **40% fewer tokens** than JSON
|
|
196
|
+
- **Lossless roundtrip** โ no data loss
|
|
197
|
+
- **Better LLM comprehension** โ structured for AI consumption
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
git clone https://github.com/LuiggiVal08/toon-memory.git
|
|
205
|
+
cd toon-memory
|
|
206
|
+
npm install
|
|
207
|
+
npm run build
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Credits
|
|
219
|
+
|
|
220
|
+
Built with [@toon-format/toon](https://www.npmjs.com/package/@toon-format/toon) and [@modelcontextprotocol/server](https://www.npmjs.com/package/@modelcontextprotocol/server).
|
package/bin/setup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from "fs"
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs"
|
|
3
3
|
import { dirname, join } from "path"
|
|
4
4
|
import { fileURLToPath } from "url"
|
|
5
5
|
import { execSync } from "child_process"
|
|
@@ -9,7 +9,7 @@ import { createRequire } from "module"
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
10
|
const projectRoot = process.cwd()
|
|
11
11
|
const sourceDir = join(__dirname, "..", "src")
|
|
12
|
-
const HOME = process.env.HOME || "~"
|
|
12
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || "~"
|
|
13
13
|
|
|
14
14
|
// Auto-install @toon-format/toon if not present
|
|
15
15
|
try {
|
|
@@ -26,89 +26,62 @@ function detectAgents() {
|
|
|
26
26
|
// OpenCode
|
|
27
27
|
const opencodeGlobal = join(HOME, ".config", "opencode", "opencode.json")
|
|
28
28
|
const opencodeLocal = join(projectRoot, ".opencode", "opencode.json")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
}
|
|
29
|
+
agents.push({
|
|
30
|
+
name: "opencode",
|
|
31
|
+
global: opencodeGlobal,
|
|
32
|
+
local: opencodeLocal,
|
|
33
|
+
mcpKey: "mcp"
|
|
34
|
+
})
|
|
37
35
|
|
|
38
36
|
// VS Code / GitHub Copilot
|
|
39
37
|
const vscodeLocal = join(projectRoot, ".vscode", "mcp.json")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
local: vscodeLocal,
|
|
46
|
-
mcpKey: "servers"
|
|
47
|
-
})
|
|
48
|
-
}
|
|
38
|
+
agents.push({
|
|
39
|
+
name: "vscode/copilot",
|
|
40
|
+
local: vscodeLocal,
|
|
41
|
+
mcpKey: "servers"
|
|
42
|
+
})
|
|
49
43
|
|
|
50
44
|
// Claude Code
|
|
51
45
|
const claudeGlobal = join(HOME, ".claude", "settings.json")
|
|
52
46
|
const claudeLocal = join(projectRoot, ".claude", "settings.json")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
}
|
|
47
|
+
agents.push({
|
|
48
|
+
name: "claude",
|
|
49
|
+
global: claudeGlobal,
|
|
50
|
+
local: claudeLocal,
|
|
51
|
+
mcpKey: "mcpServers"
|
|
52
|
+
})
|
|
61
53
|
|
|
62
54
|
// Cursor
|
|
63
55
|
const cursorLocal = join(projectRoot, ".cursor", "mcp.json")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
}
|
|
56
|
+
agents.push({
|
|
57
|
+
name: "cursor",
|
|
58
|
+
local: cursorLocal,
|
|
59
|
+
mcpKey: "mcpServers"
|
|
60
|
+
})
|
|
71
61
|
|
|
72
62
|
// Windsurf
|
|
73
|
-
const windsurfLocal = join(projectRoot, ".windsurfrules")
|
|
74
63
|
const windsurfGlobal = join(HOME, ".codeium", "windsurf", "mcp_config.json")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
mcpKey: "mcpServers"
|
|
81
|
-
})
|
|
82
|
-
}
|
|
64
|
+
agents.push({
|
|
65
|
+
name: "windsurf",
|
|
66
|
+
global: windsurfGlobal,
|
|
67
|
+
mcpKey: "mcpServers"
|
|
68
|
+
})
|
|
83
69
|
|
|
84
70
|
// Cline
|
|
85
71
|
const clineLocal = join(projectRoot, ".cline", "mcp.json")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
92
|
-
}
|
|
72
|
+
agents.push({
|
|
73
|
+
name: "cline",
|
|
74
|
+
local: clineLocal,
|
|
75
|
+
mcpKey: "mcpServers"
|
|
76
|
+
})
|
|
93
77
|
|
|
94
78
|
// Continue
|
|
95
79
|
const continueLocal = join(projectRoot, ".continue", "config.json")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Aider
|
|
105
|
-
const aiderLocal = join(projectRoot, ".aider.conf.yml")
|
|
106
|
-
if (existsSync(aiderLocal)) {
|
|
107
|
-
agents.push({
|
|
108
|
-
name: "aider",
|
|
109
|
-
local: aiderLocal
|
|
110
|
-
})
|
|
111
|
-
}
|
|
80
|
+
agents.push({
|
|
81
|
+
name: "continue",
|
|
82
|
+
local: continueLocal,
|
|
83
|
+
mcpKey: "mcpServers"
|
|
84
|
+
})
|
|
112
85
|
|
|
113
86
|
return agents
|
|
114
87
|
}
|
|
@@ -152,44 +125,171 @@ function installMCPConfig(agent, scope) {
|
|
|
152
125
|
}
|
|
153
126
|
}
|
|
154
127
|
|
|
155
|
-
const mcpKey = agent.mcpKey || "
|
|
128
|
+
const mcpKey = agent.mcpKey || "mcpServers"
|
|
156
129
|
if (!config[mcpKey]) config[mcpKey] = {}
|
|
157
130
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
131
|
+
config[mcpKey]["toon-memory"] = {
|
|
132
|
+
command: "npx",
|
|
133
|
+
args: ["-y", "toon-memory", "mcp"]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
137
|
+
console.log(` MCP server added to ${configPath}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Uninstall from all agents
|
|
141
|
+
function uninstall() {
|
|
142
|
+
console.log("\n๐ง toon-memory uninstaller\n")
|
|
143
|
+
|
|
144
|
+
const agents = detectAgents()
|
|
145
|
+
|
|
146
|
+
for (const agent of agents) {
|
|
147
|
+
const configs = [agent.global, agent.local].filter(Boolean)
|
|
148
|
+
|
|
149
|
+
for (const configPath of configs) {
|
|
150
|
+
if (!existsSync(configPath)) continue
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
154
|
+
const mcpKey = agent.mcpKey || "mcpServers"
|
|
155
|
+
|
|
156
|
+
if (config[mcpKey]?.["toon-memory"]) {
|
|
157
|
+
delete config[mcpKey]["toon-memory"]
|
|
158
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
159
|
+
console.log(` โ
Removed from ${agent.name} (${configPath})`)
|
|
160
|
+
}
|
|
161
|
+
} catch {}
|
|
164
162
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Remove custom tools
|
|
166
|
+
const toolsFile = join(projectRoot, ".opencode", "tools", "memory.ts")
|
|
167
|
+
if (existsSync(toolsFile)) {
|
|
168
|
+
unlinkSync(toolsFile)
|
|
169
|
+
console.log(" โ
Removed .opencode/tools/memory.ts")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log("\nโ
toon-memory uninstalled from all agents\n")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Quick init without interactive prompts
|
|
176
|
+
function init(scope = "local") {
|
|
177
|
+
console.log("\n๐ง toon-memory init\n")
|
|
178
|
+
|
|
179
|
+
const agents = detectAgents()
|
|
180
|
+
|
|
181
|
+
for (const agent of agents) {
|
|
182
|
+
console.log(`${agent.name}:`)
|
|
183
|
+
|
|
184
|
+
if (agent.name === "opencode") {
|
|
185
|
+
installOpenCodeTools()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
installMCPConfig(agent, scope)
|
|
189
|
+
console.log("")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log("Done! Restart your agent to use memory tools.\n")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Show installation status
|
|
196
|
+
function status() {
|
|
197
|
+
console.log("\n๐ง toon-memory status\n")
|
|
198
|
+
|
|
199
|
+
// Check npm package
|
|
200
|
+
try {
|
|
201
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"))
|
|
202
|
+
console.log(`Version: ${pkg.version}`)
|
|
203
|
+
} catch {
|
|
204
|
+
console.log("Version: unknown")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check memory file
|
|
208
|
+
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon")
|
|
209
|
+
if (existsSync(memoryFile)) {
|
|
210
|
+
const data = readFileSync(memoryFile, "utf-8")
|
|
211
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"))
|
|
212
|
+
console.log(`Memory: ${lines.length} entries`)
|
|
170
213
|
} else {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
214
|
+
console.log("Memory: not initialized")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check agent configs
|
|
218
|
+
const agents = detectAgents()
|
|
219
|
+
console.log("\nAgent configs:")
|
|
220
|
+
|
|
221
|
+
for (const agent of agents) {
|
|
222
|
+
const configs = [agent.global, agent.local].filter(Boolean)
|
|
223
|
+
let found = false
|
|
224
|
+
|
|
225
|
+
for (const configPath of configs) {
|
|
226
|
+
if (!existsSync(configPath)) continue
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
230
|
+
const mcpKey = agent.mcpKey || "mcpServers"
|
|
231
|
+
|
|
232
|
+
if (config[mcpKey]?.["toon-memory"]) {
|
|
233
|
+
console.log(` โ
${agent.name} (${configPath})`)
|
|
234
|
+
found = true
|
|
235
|
+
}
|
|
236
|
+
} catch {}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!found) {
|
|
240
|
+
console.log(` โ ${agent.name} (not configured)`)
|
|
174
241
|
}
|
|
175
242
|
}
|
|
176
243
|
|
|
177
|
-
|
|
178
|
-
|
|
244
|
+
console.log("")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Upgrade to latest version
|
|
248
|
+
function upgrade() {
|
|
249
|
+
console.log("\n๐ง toon-memory upgrade\n")
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
console.log("Checking for updates...")
|
|
253
|
+
const latest = execSync("npm view toon-memory version", { encoding: "utf-8" }).trim()
|
|
254
|
+
console.log(`Latest version: ${latest}`)
|
|
255
|
+
|
|
256
|
+
console.log("Upgrading...")
|
|
257
|
+
execSync("npm install -g toon-memory@" + latest, { stdio: "inherit" })
|
|
258
|
+
|
|
259
|
+
console.log("\nโ
Upgraded to toon-memory@" + latest)
|
|
260
|
+
console.log("Restart your agent to use the new version.\n")
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("Upgrade failed:", error.message)
|
|
263
|
+
}
|
|
179
264
|
}
|
|
180
265
|
|
|
181
266
|
// Main
|
|
182
|
-
const
|
|
183
|
-
|
|
267
|
+
const args = process.argv.slice(2)
|
|
268
|
+
|
|
269
|
+
if (args[0] === "uninstall") {
|
|
270
|
+
uninstall()
|
|
271
|
+
process.exit(0)
|
|
272
|
+
}
|
|
184
273
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
installOpenCodeTools()
|
|
188
|
-
console.log("\nDone! Restart your agent to use memory tools.")
|
|
274
|
+
if (args[0] === "init") {
|
|
275
|
+
init(args[1] || "local")
|
|
189
276
|
process.exit(0)
|
|
190
277
|
}
|
|
191
278
|
|
|
192
|
-
|
|
279
|
+
if (args[0] === "status") {
|
|
280
|
+
status()
|
|
281
|
+
process.exit(0)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (args[0] === "upgrade") {
|
|
285
|
+
upgrade()
|
|
286
|
+
process.exit(0)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const agents = detectAgents()
|
|
290
|
+
console.log("\n๐ง toon-memory installer\n")
|
|
291
|
+
|
|
292
|
+
console.log("Supported agents:")
|
|
193
293
|
agents.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`))
|
|
194
294
|
console.log("")
|
|
195
295
|
|
|
@@ -210,5 +310,6 @@ rl.question("Install (1) Local or (2) Global? [1/2]: ", (answer) => {
|
|
|
210
310
|
}
|
|
211
311
|
|
|
212
312
|
console.log("Done! Restart your agent to use memory tools.")
|
|
313
|
+
console.log("Run 'npx toon-memory uninstall' to remove.\n")
|
|
213
314
|
rl.close()
|
|
214
315
|
})
|
package/install.ps1
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# toon-memory installer for Windows (PowerShell)
|
|
2
|
+
# Usage: irm https://raw.githubusercontent.com/LuiggiVal08/toon-memory/main/install.ps1 | iex
|
|
3
|
+
|
|
4
|
+
$TOON_VERSION = "1.0.9"
|
|
5
|
+
|
|
6
|
+
Write-Host "๐ง toon-memory installer" -ForegroundColor Cyan
|
|
7
|
+
Write-Host ""
|
|
8
|
+
|
|
9
|
+
# Check if npm is available
|
|
10
|
+
if (Get-Command npm -ErrorAction SilentlyContinue) {
|
|
11
|
+
Write-Host "Installing toon-memory via npm..."
|
|
12
|
+
npm install -g toon-memory@$TOON_VERSION
|
|
13
|
+
Write-Host ""
|
|
14
|
+
Write-Host "โ
toon-memory installed!" -ForegroundColor Green
|
|
15
|
+
Write-Host ""
|
|
16
|
+
Write-Host "Next steps:"
|
|
17
|
+
Write-Host " 1. Run: npx toon-memory"
|
|
18
|
+
Write-Host " 2. Select your agent(s)"
|
|
19
|
+
Write-Host " 3. Choose local or global install"
|
|
20
|
+
Write-Host ""
|
|
21
|
+
} else {
|
|
22
|
+
Write-Host "npm not found. Installing standalone..." -ForegroundColor Yellow
|
|
23
|
+
Write-Host ""
|
|
24
|
+
|
|
25
|
+
# Create installation directory
|
|
26
|
+
$INSTALL_DIR = "$env:USERPROFILE\.toon-memory"
|
|
27
|
+
New-Item -ItemType Directory -Force -Path $INSTALL_DIR | Out-Null
|
|
28
|
+
|
|
29
|
+
# Download the package
|
|
30
|
+
Write-Host "Downloading toon-memory v$TOON_VERSION..."
|
|
31
|
+
$url = "https://registry.npmjs.org/toon-memory/-/toon-memory-$TOON_VERSION.tgz"
|
|
32
|
+
$tgzPath = "$INSTALL_DIR\toon-memory.tgz"
|
|
33
|
+
Invoke-WebRequest -Uri $url -OutFile $tgzPath
|
|
34
|
+
|
|
35
|
+
# Extract
|
|
36
|
+
cd $INSTALL_DIR
|
|
37
|
+
tar xzf toon-memory.tgz
|
|
38
|
+
Move-Item -Path "package\*" -Destination "." -Force
|
|
39
|
+
Remove-Item -Path "package" -Recurse -Force
|
|
40
|
+
Remove-Item -Path $tgzPath -Force
|
|
41
|
+
|
|
42
|
+
# Create launcher script
|
|
43
|
+
$launcherContent = @"
|
|
44
|
+
`$SCRIPT_DIR = Split-Path -Parent `$MyInvocation.MyCommand.Path
|
|
45
|
+
node "`$SCRIPT_DIR\bin\toon-memory.js" @args
|
|
46
|
+
"@
|
|
47
|
+
Set-Content -Path "$INSTALL_DIR\toon-memory.ps1" -Value $launcherContent
|
|
48
|
+
|
|
49
|
+
# Add to PATH if not already there
|
|
50
|
+
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
51
|
+
if ($currentPath -notlike "*$INSTALL_DIR*") {
|
|
52
|
+
[Environment]::SetEnvironmentVariable("Path", "$INSTALL_DIR;$currentPath", "User")
|
|
53
|
+
Write-Host "Added to PATH"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Write-Host ""
|
|
57
|
+
Write-Host "โ
toon-memory installed to $INSTALL_DIR" -ForegroundColor Green
|
|
58
|
+
Write-Host ""
|
|
59
|
+
Write-Host "โ ๏ธ Open a new PowerShell window, then run:"
|
|
60
|
+
Write-Host " toon-memory.ps1"
|
|
61
|
+
Write-Host ""
|
|
62
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# toon-memory installer for macOS/Linux
|
|
5
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/luiggival/toon-memory/main/install.sh | sh
|
|
6
|
+
|
|
7
|
+
TOON_VERSION="1.0.8"
|
|
8
|
+
NPM_REGISTRY="https://registry.npmjs.org"
|
|
9
|
+
|
|
10
|
+
echo "๐ง toon-memory installer"
|
|
11
|
+
echo ""
|
|
12
|
+
|
|
13
|
+
# Check if npm is available
|
|
14
|
+
if command -v npm &> /dev/null; then
|
|
15
|
+
echo "Installing toon-memory via npm..."
|
|
16
|
+
npm install -g toon-memory@${TOON_VERSION}
|
|
17
|
+
echo ""
|
|
18
|
+
echo "โ
toon-memory installed!"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Next steps:"
|
|
21
|
+
echo " 1. Run: npx toon-memory"
|
|
22
|
+
echo " 2. Select your agent(s)"
|
|
23
|
+
echo " 3. Choose local or global install"
|
|
24
|
+
echo ""
|
|
25
|
+
else
|
|
26
|
+
echo "npm not found. Installing standalone..."
|
|
27
|
+
echo ""
|
|
28
|
+
|
|
29
|
+
# Create installation directory
|
|
30
|
+
INSTALL_DIR="${HOME}/.toon-memory"
|
|
31
|
+
mkdir -p "${INSTALL_DIR}"
|
|
32
|
+
|
|
33
|
+
# Download the package
|
|
34
|
+
echo "Downloading toon-memory v${TOON_VERSION}..."
|
|
35
|
+
curl -fsSL "https://registry.npmjs.org/toon-memory/-/toon-memory-${TOON_VERSION}.tgz" -o "${INSTALL_DIR}/toon-memory.tgz"
|
|
36
|
+
|
|
37
|
+
# Extract
|
|
38
|
+
cd "${INSTALL_DIR}"
|
|
39
|
+
tar xzf toon-memory.tgz
|
|
40
|
+
mv package/* .
|
|
41
|
+
rmdir package
|
|
42
|
+
rm toon-memory.tgz
|
|
43
|
+
|
|
44
|
+
# Create launcher script
|
|
45
|
+
cat > "${INSTALL_DIR}/toon-memory" << 'LAUNCHER'
|
|
46
|
+
#!/bin/bash
|
|
47
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
48
|
+
node "${SCRIPT_DIR}/bin/toon-memory.js" "$@"
|
|
49
|
+
LAUNCHER
|
|
50
|
+
chmod +x "${INSTALL_DIR}/toon-memory"
|
|
51
|
+
|
|
52
|
+
# Add to PATH if not already there
|
|
53
|
+
SHELL_RC=""
|
|
54
|
+
if [ -f "${HOME}/.bashrc" ]; then
|
|
55
|
+
SHELL_RC="${HOME}/.bashrc"
|
|
56
|
+
elif [ -f "${HOME}/.zshrc" ]; then
|
|
57
|
+
SHELL_RC="${HOME}/.zshrc"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [ -n "${SHELL_RC}" ]; then
|
|
61
|
+
if ! grep -q "${INSTALL_DIR}" "${SHELL_RC}" 2>/dev/null; then
|
|
62
|
+
echo "" >> "${SHELL_RC}"
|
|
63
|
+
echo "# toon-memory" >> "${SHELL_RC}"
|
|
64
|
+
echo "export PATH=\"${INSTALL_DIR}:\$PATH\"" >> "${SHELL_RC}"
|
|
65
|
+
echo "Added to PATH in ${SHELL_RC}"
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo ""
|
|
70
|
+
echo "โ
toon-memory installed to ${INSTALL_DIR}"
|
|
71
|
+
echo ""
|
|
72
|
+
echo "โ ๏ธ Open a new terminal, then run:"
|
|
73
|
+
echo " toon-memory"
|
|
74
|
+
echo ""
|
|
75
|
+
fi
|
package/mcp/server.js
CHANGED
|
@@ -623,7 +623,7 @@ function $constructor(name, initializer3, params) {
|
|
|
623
623
|
Object.defineProperty(_, "name", { value: name });
|
|
624
624
|
return _;
|
|
625
625
|
}
|
|
626
|
-
var $brand =
|
|
626
|
+
var $brand = Symbol("zod_brand");
|
|
627
627
|
var $ZodAsyncError = class extends Error {
|
|
628
628
|
constructor() {
|
|
629
629
|
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
|
@@ -10366,8 +10366,8 @@ function yo_default() {
|
|
|
10366
10366
|
|
|
10367
10367
|
// node_modules/zod/v4/core/registries.js
|
|
10368
10368
|
var _a2;
|
|
10369
|
-
var $output =
|
|
10370
|
-
var $input =
|
|
10369
|
+
var $output = Symbol("ZodOutput");
|
|
10370
|
+
var $input = Symbol("ZodInput");
|
|
10371
10371
|
var $ZodRegistry = class {
|
|
10372
10372
|
constructor() {
|
|
10373
10373
|
this._map = /* @__PURE__ */ new WeakMap();
|
|
@@ -21148,14 +21148,14 @@ var Ajv = import_ajv.Ajv;
|
|
|
21148
21148
|
import process3 from "node:process";
|
|
21149
21149
|
|
|
21150
21150
|
// node_modules/@modelcontextprotocol/server/dist/mcp-CM8J1mhp.mjs
|
|
21151
|
-
var COMPLETABLE_SYMBOL =
|
|
21151
|
+
var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
|
21152
21152
|
function isCompletable(schema) {
|
|
21153
21153
|
return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
|
|
21154
21154
|
}
|
|
21155
21155
|
function getCompleter(schema) {
|
|
21156
21156
|
return schema[COMPLETABLE_SYMBOL]?.complete;
|
|
21157
21157
|
}
|
|
21158
|
-
var BRANDS =
|
|
21158
|
+
var BRANDS = Symbol.for("mcp.sdk.errorBrands");
|
|
21159
21159
|
function stampErrorBrands(instance, ctor) {
|
|
21160
21160
|
const brands = /* @__PURE__ */ new Set();
|
|
21161
21161
|
let current = ctor;
|
|
@@ -22468,7 +22468,7 @@ var CACHEABLE_RESULT_METHODS = [
|
|
|
22468
22468
|
function isCacheableResultMethod(method) {
|
|
22469
22469
|
return CACHEABLE_RESULT_METHODS.includes(method);
|
|
22470
22470
|
}
|
|
22471
|
-
var RESULT_CACHE_HINT_FALLBACK =
|
|
22471
|
+
var RESULT_CACHE_HINT_FALLBACK = Symbol("modelcontextprotocol.resultCacheHintFallback");
|
|
22472
22472
|
function attachCacheHintFallback(result, hint) {
|
|
22473
22473
|
if (hint === void 0) return result;
|
|
22474
22474
|
const attached = result[RESULT_CACHE_HINT_FALLBACK];
|
|
@@ -27664,7 +27664,7 @@ function generateId() {
|
|
|
27664
27664
|
return randomBytes(4).toString("hex");
|
|
27665
27665
|
}
|
|
27666
27666
|
var server = new McpServer(
|
|
27667
|
-
{ name: "toon-memory", version: "1.0
|
|
27667
|
+
{ name: "toon-memory", version: "1.1.0" },
|
|
27668
27668
|
{ capabilities: { tools: { listChanged: true } } }
|
|
27669
27669
|
);
|
|
27670
27670
|
server.registerTool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toon-memory",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Persistent memory system for AI coding agents using TOON format (40% fewer tokens than JSON)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,13 +10,23 @@
|
|
|
10
10
|
"src/",
|
|
11
11
|
"bin/",
|
|
12
12
|
"mcp/",
|
|
13
|
-
"skills/"
|
|
13
|
+
"skills/",
|
|
14
|
+
"install.sh",
|
|
15
|
+
"install.ps1",
|
|
16
|
+
"uninstall.sh"
|
|
14
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "esbuild src/mcp/server.ts --bundle --platform=node --format=esm --outfile=mcp/server.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
15
22
|
"dependencies": {
|
|
16
23
|
"@modelcontextprotocol/server": "^2.0.0-beta.3",
|
|
17
24
|
"@toon-format/toon": "^2.3.0",
|
|
18
25
|
"zod": "^4.4.3"
|
|
19
26
|
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"esbuild": "^0.25.0"
|
|
29
|
+
},
|
|
20
30
|
"keywords": [
|
|
21
31
|
"opencode",
|
|
22
32
|
"vscode",
|
|
@@ -33,5 +43,14 @@
|
|
|
33
43
|
"llm",
|
|
34
44
|
"token-efficiency"
|
|
35
45
|
],
|
|
36
|
-
"
|
|
46
|
+
"author": "luiggival",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/luiggival/toon-memory.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/luiggival/toon-memory#readme",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/luiggival/toon-memory/issues"
|
|
55
|
+
}
|
|
37
56
|
}
|
package/src/mcp/server.ts
CHANGED
package/uninstall.sh
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# toon-memory uninstaller
|
|
5
|
+
# Usage: npx toon-memory uninstall
|
|
6
|
+
|
|
7
|
+
echo "๐ง toon-memory uninstaller"
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# Remove from OpenCode
|
|
11
|
+
OPENCODE_GLOBAL="${HOME}/.config/opencode/opencode.json"
|
|
12
|
+
OPENCODE_LOCAL=".opencode/opencode.json"
|
|
13
|
+
|
|
14
|
+
remove_from_config() {
|
|
15
|
+
local file="$1"
|
|
16
|
+
local name="$2"
|
|
17
|
+
|
|
18
|
+
if [ -f "${file}" ]; then
|
|
19
|
+
if grep -q "toon-memory" "${file}" 2>/dev/null; then
|
|
20
|
+
echo "Removing from ${name} (${file})..."
|
|
21
|
+
# Use node to remove the MCP entry
|
|
22
|
+
node -e "
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const config = JSON.parse(fs.readFileSync('${file}', 'utf-8'));
|
|
25
|
+
if (config.mcp && config.mcp['toon-memory']) {
|
|
26
|
+
delete config.mcp['toon-memory'];
|
|
27
|
+
fs.writeFileSync('${file}', JSON.stringify(config, null, 2));
|
|
28
|
+
console.log(' โ
Removed');
|
|
29
|
+
} else if (config.mcpServers && config.mcpServers['toon-memory']) {
|
|
30
|
+
delete config.mcpServers['toon-memory'];
|
|
31
|
+
fs.writeFileSync('${file}', JSON.stringify(config, null, 2));
|
|
32
|
+
console.log(' โ
Removed');
|
|
33
|
+
} else {
|
|
34
|
+
console.log(' โ ๏ธ Not found');
|
|
35
|
+
}
|
|
36
|
+
" 2>/dev/null || echo " โ ๏ธ Could not parse config"
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# OpenCode
|
|
42
|
+
remove_from_config "${OPENCODE_GLOBAL}" "OpenCode (global)"
|
|
43
|
+
remove_from_config "${OPENCODE_LOCAL}" "OpenCode (local)"
|
|
44
|
+
|
|
45
|
+
# VS Code
|
|
46
|
+
remove_from_config ".vscode/mcp.json" "VS Code"
|
|
47
|
+
|
|
48
|
+
# Claude
|
|
49
|
+
remove_from_config "${HOME}/.claude/settings.json" "Claude (global)"
|
|
50
|
+
remove_from_config ".claude/settings.json" "Claude (local)"
|
|
51
|
+
|
|
52
|
+
# Cursor
|
|
53
|
+
remove_from_config ".cursor/mcp.json" "Cursor"
|
|
54
|
+
|
|
55
|
+
# Cline
|
|
56
|
+
remove_from_config ".cline/mcp.json" "Cline"
|
|
57
|
+
|
|
58
|
+
# Continue
|
|
59
|
+
remove_from_config ".continue/config.json" "Continue"
|
|
60
|
+
|
|
61
|
+
# Remove custom tools if they exist
|
|
62
|
+
if [ -d ".opencode/tools" ]; then
|
|
63
|
+
if [ -f ".opencode/tools/memory.ts" ]; then
|
|
64
|
+
echo "Removing custom tools..."
|
|
65
|
+
rm -f ".opencode/tools/memory.ts"
|
|
66
|
+
echo " โ
Removed memory.ts"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Remove memory file (ask user)
|
|
71
|
+
if [ -f ".opencode/memory/data.toon" ]; then
|
|
72
|
+
echo ""
|
|
73
|
+
read -p "Remove memory file (.opencode/memory/data.toon)? [y/N] " -n 1 -r
|
|
74
|
+
echo
|
|
75
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
76
|
+
rm -rf ".opencode/memory"
|
|
77
|
+
echo " โ
Removed memory directory"
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Uninstall npm package
|
|
82
|
+
echo ""
|
|
83
|
+
read -p "Uninstall toon-memory CLI? [y/N] " -n 1 -r
|
|
84
|
+
echo
|
|
85
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
86
|
+
if command -v npm &> /dev/null; then
|
|
87
|
+
npm uninstall -g toon-memory 2>/dev/null && echo " โ
Uninstalled from npm" || echo " โ ๏ธ Not installed via npm"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Remove standalone install
|
|
91
|
+
if [ -d "${HOME}/.toon-memory" ]; then
|
|
92
|
+
rm -rf "${HOME}/.toon-memory"
|
|
93
|
+
echo " โ
Removed standalone install"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Remove from PATH in shell configs
|
|
97
|
+
for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do
|
|
98
|
+
if [ -f "${rc}" ] && grep -q "toon-memory" "${rc}" 2>/dev/null; then
|
|
99
|
+
sed -i '/# toon-memory/d;/.toon-memory/d' "${rc}"
|
|
100
|
+
echo " โ
Cleaned ${rc}"
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo ""
|
|
106
|
+
echo "โ
toon-memory uninstalled"
|
|
107
|
+
echo ""
|