vscode-terminal-mcp 0.1.5 → 0.1.6
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/CHANGELOG.md +45 -0
- package/CLAUDE.md +84 -0
- package/README.md +96 -0
- package/dist/extension.js +49 -42
- package/dist/mcp-entry.js +14 -1
- package/docs/images/ask_exec_permission.png +0 -0
- package/docs/images/exec_finished.png +0 -0
- package/docs/images/run_finished.png +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.6] - 2026-03-19 18:18 PDT
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Screenshots in README for marketplace (run, exec, permission dialog)
|
|
9
|
+
- Custom terminal tab names with date format (e.g., `MCP: BashTerm-26-03-19-17-30`)
|
|
10
|
+
- `name` parameter in `run` tool for custom terminal names
|
|
11
|
+
- Unique IPC socket per workspace to prevent conflicts between multiple VSCode instances
|
|
12
|
+
- Large output handling documentation
|
|
13
|
+
- Development workflow docs for extension cache workaround
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Clean output format for all tools (`run`, `exec`, `read`, `list`, `close`, `input`) — no more raw JSON responses
|
|
17
|
+
- `waitForCompletion: false` not working (`z.coerce.boolean()` converted string `"false"` to `true`)
|
|
18
|
+
- Idle reaper killing sessions with running commands — reaper disabled, user closes sessions manually
|
|
19
|
+
|
|
20
|
+
## [0.1.5] - 2026-03-18 14:50 PDT
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- npm publish with `bin` entry for `npx vscode-terminal-mcp` support
|
|
24
|
+
- Published to VSCode Marketplace and MCP Registry
|
|
25
|
+
|
|
26
|
+
## [0.1.3] - 2026-03-18 11:00 PDT
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- `run` tool combining create + exec in one step
|
|
30
|
+
- Session reuse: `run` finds idle sessions before creating new ones
|
|
31
|
+
- Busy session detection: won't reuse sessions with running commands
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- First-command timing fix with shell initialization delay
|
|
35
|
+
|
|
36
|
+
## [0.1.0] - 2026-03-18 10:00 PDT
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- Initial release
|
|
40
|
+
- Tools: `create`, `exec`, `read`, `input`, `list`, `close`
|
|
41
|
+
- Shell Integration API for output capture and exit code detection
|
|
42
|
+
- Circular output buffer with pagination support
|
|
43
|
+
- Subagent isolation with `agentId`
|
|
44
|
+
- Command blocklist security
|
|
45
|
+
- IPC bridge for MCP stdio-to-socket communication
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Project: vscode-terminal-mcp
|
|
2
|
+
|
|
3
|
+
MCP server that runs commands in visible VSCode terminal tabs.
|
|
4
|
+
|
|
5
|
+
## Release Process
|
|
6
|
+
|
|
7
|
+
When publishing a new version, follow these steps in order:
|
|
8
|
+
|
|
9
|
+
### 1. Update version
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# In package.json, bump the version
|
|
13
|
+
# e.g., "version": "0.1.6" → "version": "0.1.7"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. Update CHANGELOG.md
|
|
17
|
+
|
|
18
|
+
Add a new entry at the top with the new version and date:
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
## [0.1.7] - YYYY-MM-DD
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- ...
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- ...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Update README.md
|
|
31
|
+
|
|
32
|
+
Replace the "Latest Changes" section with the new version's changes. Keep only the latest version in README — full history lives in CHANGELOG.md.
|
|
33
|
+
|
|
34
|
+
### 4. Build and publish
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Build
|
|
38
|
+
npm run build
|
|
39
|
+
|
|
40
|
+
# Publish to npm
|
|
41
|
+
npm publish --access public
|
|
42
|
+
|
|
43
|
+
# Package vsix
|
|
44
|
+
npx vsce package --allow-missing-repository
|
|
45
|
+
|
|
46
|
+
# Install locally for testing
|
|
47
|
+
cp dist/extension.js dist/mcp-entry.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 5. Upload to VSCode Marketplace
|
|
51
|
+
|
|
52
|
+
1. Go to https://marketplace.visualstudio.com/manage/publishers/sirlordt
|
|
53
|
+
2. Click "..." next to Terminal MCP → "Update"
|
|
54
|
+
3. Upload the `.vsix` file
|
|
55
|
+
|
|
56
|
+
### 6. Commit and push
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git add -A
|
|
60
|
+
git commit -m "v0.1.7: <summary of changes>"
|
|
61
|
+
git push
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Extension Cache Workaround
|
|
65
|
+
|
|
66
|
+
VSCode aggressively caches extensions. When developing locally:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Quick update (after modifying source)
|
|
70
|
+
npm run build
|
|
71
|
+
cp dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/extension.js
|
|
72
|
+
# Then "Developer: Reload Window"
|
|
73
|
+
|
|
74
|
+
# If reload doesn't pick up changes, close and reopen VSCode completely
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Terminal Execution
|
|
78
|
+
|
|
79
|
+
Prefer the BashTerm MCP tools (`run`, `exec`, `read`, etc.) over the built-in Bash tool for executing commands. BashTerm runs commands in visible VSCode terminal tabs where the user can see output in real time.
|
|
80
|
+
|
|
81
|
+
For commands that may take longer than 30 seconds or produce large output, use pull mode:
|
|
82
|
+
1. Call `run` with `waitForCompletion: false`
|
|
83
|
+
2. Call `read` with `offset: -10` to check progress
|
|
84
|
+
3. Repeat until done
|
package/README.md
CHANGED
|
@@ -84,6 +84,20 @@ After installation, try asking:
|
|
|
84
84
|
|
|
85
85
|
You should see a new terminal tab open in VSCode with the command output.
|
|
86
86
|
|
|
87
|
+
## Screenshots
|
|
88
|
+
|
|
89
|
+
### Running a command with `run`
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
### Permission dialog for `exec`
|
|
94
|
+
|
|
95
|
+

|
|
96
|
+
|
|
97
|
+
### Exec result with clean output
|
|
98
|
+
|
|
99
|
+

|
|
100
|
+
|
|
87
101
|
## Tools
|
|
88
102
|
|
|
89
103
|
### Quick Execution
|
|
@@ -202,6 +216,76 @@ This prevents conversation timeouts and lets the user watch progress in the term
|
|
|
202
216
|
| Session state | Each command is isolated | Persistent sessions with history |
|
|
203
217
|
| Interactive commands | Not supported | Send input to prompts/REPLs |
|
|
204
218
|
|
|
219
|
+
## Development: Updating the Extension
|
|
220
|
+
|
|
221
|
+
VSCode aggressively caches extensions in memory. When developing locally, `code --install-extension` and even "Developer: Reload Window" may **not** reload your changes. Use this workflow:
|
|
222
|
+
|
|
223
|
+
### Quick update (no restart needed)
|
|
224
|
+
|
|
225
|
+
After modifying source files, build and copy directly into the installed extension directory:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cd /path/to/vscode-terminal-mcp
|
|
229
|
+
npm run build
|
|
230
|
+
cp dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-<version>/dist/extension.js
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Then run **"Developer: Reload Window"** (`Ctrl+Shift+P`).
|
|
234
|
+
|
|
235
|
+
### Full reinstall (when quick update doesn't work)
|
|
236
|
+
|
|
237
|
+
If VSCode still uses old code:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# 1. Uninstall and remove all copies
|
|
241
|
+
code --uninstall-extension sirlordt.vscode-terminal-mcp
|
|
242
|
+
rm -rf ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*
|
|
243
|
+
|
|
244
|
+
# 2. Check for ghost entries with old publisher names
|
|
245
|
+
# Look in ~/.vscode/extensions/extensions.json for stale entries
|
|
246
|
+
# Remove any entries with old publisher IDs (e.g., "terminal-mcp.vscode-terminal-mcp")
|
|
247
|
+
|
|
248
|
+
# 3. Close VSCode completely (not just reload)
|
|
249
|
+
|
|
250
|
+
# 4. Rebuild and install
|
|
251
|
+
npm run build
|
|
252
|
+
npx vsce package --allow-missing-repository
|
|
253
|
+
code --install-extension vscode-terminal-mcp-<version>.vsix --force
|
|
254
|
+
|
|
255
|
+
# 5. Open VSCode
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Verify the correct version is loaded
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Check which extension directories exist
|
|
262
|
+
ls ~/.vscode/extensions/ | grep terminal
|
|
263
|
+
|
|
264
|
+
# Verify your changes are in the installed extension
|
|
265
|
+
grep "YOUR_UNIQUE_STRING" ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*/dist/extension.js
|
|
266
|
+
|
|
267
|
+
# Compare checksums
|
|
268
|
+
md5sum dist/extension.js ~/.vscode/extensions/sirlordt.vscode-terminal-mcp-*/dist/extension.js
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Large Output Handling
|
|
272
|
+
|
|
273
|
+
When `read` returns output that exceeds the MCP client's token limit, the system automatically saves the full output to a temporary JSON file and returns the file path in the error message.
|
|
274
|
+
|
|
275
|
+
To extract the relevant content:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Get the last 50 lines (most relevant for status)
|
|
279
|
+
tail -50 /path/to/saved/file.txt
|
|
280
|
+
|
|
281
|
+
# Or parse the JSON to extract the text content
|
|
282
|
+
python3 -c "import json; data=json.load(open('/path/to/file.txt')); print(data[0]['text'][-2000:])"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The file format is JSON: `[{"type": "text", "text": "..."}]`
|
|
286
|
+
|
|
287
|
+
This commonly happens with commands that produce heavy TUI output (progress bars, ANSI escape codes). Use smaller `offset` values (e.g., `offset: -20` instead of `offset: -100`) to reduce the captured output size.
|
|
288
|
+
|
|
205
289
|
## How It Works
|
|
206
290
|
|
|
207
291
|
1. The **VSCode extension** activates and starts an IPC server on a Unix socket
|
|
@@ -209,6 +293,18 @@ This prevents conversation timeouts and lets the user watch progress in the term
|
|
|
209
293
|
3. Commands execute in real VSCode terminals using the **Shell Integration API** for reliable output capture and exit code detection
|
|
210
294
|
4. Output is stored in circular buffers with pagination support for efficient reading
|
|
211
295
|
|
|
296
|
+
## Latest Changes (0.1.6)
|
|
297
|
+
|
|
298
|
+
- Screenshots in README for marketplace
|
|
299
|
+
- Clean output format for all tools — no more raw JSON
|
|
300
|
+
- Fixed `waitForCompletion: false` not working
|
|
301
|
+
- Disabled idle reaper — user closes sessions manually
|
|
302
|
+
- Unique IPC socket per workspace (multi-instance support)
|
|
303
|
+
- Custom terminal tab names with date format
|
|
304
|
+
- Large output handling documentation
|
|
305
|
+
|
|
306
|
+
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
307
|
+
|
|
212
308
|
## License
|
|
213
309
|
|
|
214
310
|
MIT
|
package/dist/extension.js
CHANGED
|
@@ -5446,21 +5446,13 @@ async function handleTerminalCreate(params, sessionManager2) {
|
|
|
5446
5446
|
shell: input.shell,
|
|
5447
5447
|
agentId: input.agentId
|
|
5448
5448
|
});
|
|
5449
|
+
const parts = [`Terminal created: ${sessionInfo.name}`, `session: ${sessionInfo.sessionId}`, `cwd: ${sessionInfo.cwd}`];
|
|
5450
|
+
if (sessionInfo.agentId) parts.push(`agent: ${sessionInfo.agentId}`);
|
|
5449
5451
|
return {
|
|
5450
5452
|
content: [
|
|
5451
5453
|
{
|
|
5452
5454
|
type: "text",
|
|
5453
|
-
text:
|
|
5454
|
-
{
|
|
5455
|
-
status: "created",
|
|
5456
|
-
sessionId: sessionInfo.sessionId,
|
|
5457
|
-
name: sessionInfo.name,
|
|
5458
|
-
cwd: sessionInfo.cwd,
|
|
5459
|
-
agentId: sessionInfo.agentId
|
|
5460
|
-
},
|
|
5461
|
-
null,
|
|
5462
|
-
2
|
|
5463
|
-
)
|
|
5455
|
+
text: parts.join(" | ")
|
|
5464
5456
|
}
|
|
5465
5457
|
]
|
|
5466
5458
|
};
|
|
@@ -5500,24 +5492,29 @@ async function handleTerminalExecute(params, sessionManager2) {
|
|
|
5500
5492
|
timeoutMs,
|
|
5501
5493
|
waitForCompletion
|
|
5502
5494
|
);
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
}
|
|
5495
|
+
let cleanOutput = result.output.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "").trim();
|
|
5496
|
+
const lines = cleanOutput.split("\n");
|
|
5497
|
+
if (lines.length > 0 && lines[0].trim() === input.command.trim()) {
|
|
5498
|
+
lines.shift();
|
|
5499
|
+
cleanOutput = lines.join("\n").trim();
|
|
5500
|
+
}
|
|
5501
|
+
const statusParts = [`exit: ${result.exitCode ?? "n/a"}`, `${result.durationMs}ms`, input.sessionId];
|
|
5502
|
+
let text = `$ ${input.command}
|
|
5503
|
+
${cleanOutput}
|
|
5504
|
+
|
|
5505
|
+
[${statusParts.join(" | ")}]`;
|
|
5511
5506
|
if (result.timedOut) {
|
|
5512
|
-
|
|
5507
|
+
text += `
|
|
5508
|
+
[TIMED OUT after ${timeoutMs}ms - session still active, use read to get more output]`;
|
|
5513
5509
|
}
|
|
5514
5510
|
return {
|
|
5515
5511
|
content: [
|
|
5516
5512
|
{
|
|
5517
5513
|
type: "text",
|
|
5518
|
-
text
|
|
5514
|
+
text
|
|
5519
5515
|
}
|
|
5520
|
-
]
|
|
5516
|
+
],
|
|
5517
|
+
isError: result.exitCode !== null && result.exitCode !== 0
|
|
5521
5518
|
};
|
|
5522
5519
|
}
|
|
5523
5520
|
|
|
@@ -5537,7 +5534,11 @@ async function handleTerminalRun(params, sessionManager2) {
|
|
|
5537
5534
|
}
|
|
5538
5535
|
if (!sessionId) {
|
|
5539
5536
|
const sessionInfo = sessionManager2.createSession({
|
|
5540
|
-
name: input.name ??
|
|
5537
|
+
name: input.name ?? (() => {
|
|
5538
|
+
const d = /* @__PURE__ */ new Date();
|
|
5539
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
5540
|
+
return `BashTerm-${pad(d.getFullYear() % 100)}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}-${pad(d.getMinutes())}`;
|
|
5541
|
+
})(),
|
|
5541
5542
|
cwd: input.cwd,
|
|
5542
5543
|
env: input.env,
|
|
5543
5544
|
shell: input.shell,
|
|
@@ -5634,18 +5635,23 @@ async function handleTerminalReadOutput(params, sessionManager2) {
|
|
|
5634
5635
|
async function handleTerminalList(params, sessionManager2) {
|
|
5635
5636
|
const input = terminalListSchema.parse(params ?? {});
|
|
5636
5637
|
const sessions = sessionManager2.listSessions(input.agentId);
|
|
5638
|
+
if (sessions.length === 0) {
|
|
5639
|
+
return {
|
|
5640
|
+
content: [{ type: "text", text: "No active sessions." }]
|
|
5641
|
+
};
|
|
5642
|
+
}
|
|
5643
|
+
const lines = sessions.map((s) => {
|
|
5644
|
+
const age = Math.round((Date.now() - s.createdAt) / 1e3);
|
|
5645
|
+
const parts = [s.sessionId, s.name, `cwd: ${s.cwd}`, `${age}s old`, `${s.outputLineCount} lines`];
|
|
5646
|
+
if (s.agentId) parts.push(`agent: ${s.agentId}`);
|
|
5647
|
+
return parts.join(" | ");
|
|
5648
|
+
});
|
|
5637
5649
|
return {
|
|
5638
5650
|
content: [
|
|
5639
5651
|
{
|
|
5640
5652
|
type: "text",
|
|
5641
|
-
text:
|
|
5642
|
-
|
|
5643
|
-
count: sessions.length,
|
|
5644
|
-
sessions
|
|
5645
|
-
},
|
|
5646
|
-
null,
|
|
5647
|
-
2
|
|
5648
|
-
)
|
|
5653
|
+
text: `${sessions.length} active session(s):
|
|
5654
|
+
${lines.join("\n")}`
|
|
5649
5655
|
}
|
|
5650
5656
|
]
|
|
5651
5657
|
};
|
|
@@ -5670,10 +5676,7 @@ async function handleTerminalClose(params, sessionManager2) {
|
|
|
5670
5676
|
content: [
|
|
5671
5677
|
{
|
|
5672
5678
|
type: "text",
|
|
5673
|
-
text:
|
|
5674
|
-
status: "closed",
|
|
5675
|
-
sessionId: input.sessionId
|
|
5676
|
-
})
|
|
5679
|
+
text: `Session closed: ${input.sessionId}`
|
|
5677
5680
|
}
|
|
5678
5681
|
]
|
|
5679
5682
|
};
|
|
@@ -5699,12 +5702,7 @@ async function handleTerminalSendInput(params, sessionManager2) {
|
|
|
5699
5702
|
content: [
|
|
5700
5703
|
{
|
|
5701
5704
|
type: "text",
|
|
5702
|
-
text:
|
|
5703
|
-
status: "sent",
|
|
5704
|
-
sessionId: input.sessionId,
|
|
5705
|
-
inputLength: input.input.length,
|
|
5706
|
-
pressedEnter: input.pressEnter ?? true
|
|
5707
|
-
})
|
|
5705
|
+
text: `Input sent to ${input.sessionId} (${input.input.length} chars${input.pressEnter ?? true ? " + Enter" : ""})`
|
|
5708
5706
|
}
|
|
5709
5707
|
]
|
|
5710
5708
|
};
|
|
@@ -6301,7 +6299,16 @@ var sessionManager;
|
|
|
6301
6299
|
var statusBarItem;
|
|
6302
6300
|
function getSocketPath() {
|
|
6303
6301
|
const tmpDir = os.tmpdir();
|
|
6304
|
-
|
|
6302
|
+
const crypto3 = require("crypto");
|
|
6303
|
+
const workspace4 = vscode4.workspace.workspaceFolders?.[0]?.uri.fsPath || "";
|
|
6304
|
+
const hash = crypto3.createHash("md5").update(workspace4).digest("hex").slice(0, 8);
|
|
6305
|
+
const socketPath = path.join(tmpDir, `vscode-terminal-mcp-${hash}.sock`);
|
|
6306
|
+
const discoveryPath = path.join(tmpDir, "vscode-terminal-mcp.discovery");
|
|
6307
|
+
try {
|
|
6308
|
+
fs.writeFileSync(discoveryPath, socketPath);
|
|
6309
|
+
} catch {
|
|
6310
|
+
}
|
|
6311
|
+
return socketPath;
|
|
6305
6312
|
}
|
|
6306
6313
|
function cleanupSocket(socketPath) {
|
|
6307
6314
|
try {
|
package/dist/mcp-entry.js
CHANGED
|
@@ -27,7 +27,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var net = __toESM(require("net"));
|
|
28
28
|
var path = __toESM(require("path"));
|
|
29
29
|
var os = __toESM(require("os"));
|
|
30
|
-
var
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
function getSocketPath() {
|
|
32
|
+
const tmpDir = os.tmpdir();
|
|
33
|
+
const discoveryPath = path.join(tmpDir, "vscode-terminal-mcp.discovery");
|
|
34
|
+
try {
|
|
35
|
+
const socketPath = fs.readFileSync(discoveryPath, "utf8").trim();
|
|
36
|
+
if (socketPath && fs.existsSync(socketPath)) {
|
|
37
|
+
return socketPath;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
return path.join(tmpDir, "vscode-terminal-mcp.sock");
|
|
42
|
+
}
|
|
43
|
+
var SOCKET_PATH = getSocketPath();
|
|
31
44
|
var RECONNECT_DELAY_MS = 1e3;
|
|
32
45
|
var MAX_RECONNECT_ATTEMPTS = 30;
|
|
33
46
|
var StdioToIpcBridge = class {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "vscode-terminal-mcp",
|
|
3
3
|
"displayName": "Terminal MCP Server",
|
|
4
4
|
"description": "MCP server that provides visible terminal sessions in VSCode for Claude Code and subagents",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.6",
|
|
6
6
|
"publisher": "sirlordt",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "sirlordt",
|