trueline-mcp 2.0.5 → 2.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/INSTALL.md +21 -8
- package/README.md +4 -3
- package/bin/trueline-hook.js +4 -5
- package/dist/server.js +93 -19
- package/hooks/core/access.js +7 -3
- package/hooks/core/platform.js +0 -2
- package/package.json +1 -1
package/INSTALL.md
CHANGED
|
@@ -26,7 +26,7 @@ Add to `~/.gemini/settings.json`:
|
|
|
26
26
|
"mcpServers": {
|
|
27
27
|
"trueline": {
|
|
28
28
|
"command": "npx",
|
|
29
|
-
"args": ["-y", "trueline-mcp"]
|
|
29
|
+
"args": ["-y", "trueline-mcp@latest"]
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -73,7 +73,7 @@ Add to `.vscode/mcp.json` in your project:
|
|
|
73
73
|
"servers": {
|
|
74
74
|
"trueline": {
|
|
75
75
|
"command": "npx",
|
|
76
|
-
"args": ["-y", "trueline-mcp"]
|
|
76
|
+
"args": ["-y", "trueline-mcp@latest"]
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -109,7 +109,7 @@ Add to your `opencode.json`:
|
|
|
109
109
|
"mcp": {
|
|
110
110
|
"trueline": {
|
|
111
111
|
"command": "npx",
|
|
112
|
-
"args": ["-y", "trueline-mcp"]
|
|
112
|
+
"args": ["-y", "trueline-mcp@latest"]
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -135,7 +135,7 @@ Add to `~/.codex/config.toml`:
|
|
|
135
135
|
```toml
|
|
136
136
|
[mcp_servers.trueline]
|
|
137
137
|
command = "npx"
|
|
138
|
-
args = ["-y", "trueline-mcp"]
|
|
138
|
+
args = ["-y", "trueline-mcp@latest"]
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
### 2. Add the instruction file
|
|
@@ -173,9 +173,11 @@ npm i -g trueline-mcp
|
|
|
173
173
|
|
|
174
174
|
## Path access
|
|
175
175
|
|
|
176
|
-
By default, trueline tools can access files inside the project directory
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
By default, trueline tools can access files inside the project directory.
|
|
177
|
+
When running under Claude Code, `~/.claude/` is also allowed (it stores
|
|
178
|
+
plans, memory, and settings). To allow additional directories on any
|
|
179
|
+
platform, set `TRUELINE_ALLOWED_DIRS` to a colon-separated list of paths
|
|
180
|
+
(semicolon-separated on Windows).
|
|
179
181
|
|
|
180
182
|
## Platform detection
|
|
181
183
|
|
|
@@ -184,7 +186,18 @@ The hook dispatcher auto-detects the platform from environment variables:
|
|
|
184
186
|
| Env var | Platform |
|
|
185
187
|
|------------------------|----------------|
|
|
186
188
|
| `GEMINI_PROJECT_DIR` | gemini-cli |
|
|
187
|
-
| `OPENCODE_PROJECT_DIR` | opencode |
|
|
188
189
|
| `CLAUDE_PROJECT_DIR` | claude-code |
|
|
189
190
|
|
|
190
191
|
Override with `TRUELINE_PLATFORM=<platform>` if auto-detection doesn't work.
|
|
192
|
+
|
|
193
|
+
## Keeping trueline up to date
|
|
194
|
+
|
|
195
|
+
The recommended `npx -y trueline-mcp@latest` configuration checks the npm
|
|
196
|
+
registry on each launch and always runs the newest version. If you prefer
|
|
197
|
+
faster startup and offline resilience, drop the `@latest` suffix — npx will
|
|
198
|
+
use whichever version it cached on first install. You can update manually at
|
|
199
|
+
any time with `npm i -g trueline-mcp`.
|
|
200
|
+
|
|
201
|
+
Regardless of configuration, the server prints a notice to stderr when a newer
|
|
202
|
+
version is available (checked at most once every 24 hours). This notice is
|
|
203
|
+
never sent to the agent — it only appears in MCP server logs.
|
package/README.md
CHANGED
|
@@ -171,9 +171,10 @@ are available via the `trueline-hook` CLI dispatcher — see
|
|
|
171
171
|
|
|
172
172
|
## Path access
|
|
173
173
|
|
|
174
|
-
By default, trueline tools can access files inside the project directory
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
By default, trueline tools can access files inside the project directory.
|
|
175
|
+
When running under Claude Code, `~/.claude/` is also allowed (it stores
|
|
176
|
+
plans, memory, and settings). To allow additional directories on any
|
|
177
|
+
platform, set `TRUELINE_ALLOWED_DIRS` to a colon-separated list of paths
|
|
177
178
|
(semicolon-separated on Windows).
|
|
178
179
|
|
|
179
180
|
## Development
|
package/bin/trueline-hook.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// trueline-hook gemini-cli beforetool # Gemini CLI BeforeTool hook
|
|
10
10
|
// trueline-hook gemini-cli session-start # Gemini CLI session instructions
|
|
11
11
|
// trueline-hook vscode-copilot pretooluse
|
|
12
|
-
// trueline-hook claude-code pretooluse
|
|
13
12
|
//
|
|
14
13
|
// Reads hook event JSON from stdin (for tool-use hooks), writes platform-
|
|
15
14
|
// formatted JSON to stdout.
|
|
@@ -21,13 +20,13 @@ const hooksDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "hooks")
|
|
|
21
20
|
|
|
22
21
|
const USAGE = `Usage: trueline-hook <platform> <event>
|
|
23
22
|
|
|
24
|
-
Platforms:
|
|
23
|
+
Platforms: gemini-cli, vscode-copilot
|
|
25
24
|
Events: pretooluse, beforetool, session-start
|
|
26
25
|
|
|
27
26
|
Examples:
|
|
28
27
|
trueline-hook gemini-cli beforetool
|
|
29
28
|
trueline-hook vscode-copilot pretooluse
|
|
30
|
-
trueline-hook
|
|
29
|
+
trueline-hook gemini-cli session-start`;
|
|
31
30
|
|
|
32
31
|
// ==============================================================================
|
|
33
32
|
// Argument Parsing
|
|
@@ -42,8 +41,8 @@ if (!platform || !event || process.argv.includes("--help") || process.argv.inclu
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
// Normalize event names across platforms.
|
|
45
|
-
// Gemini calls it "beforetool",
|
|
46
|
-
// to the same routing logic.
|
|
44
|
+
// Gemini calls it "beforetool", VS Code Copilot calls it "pretooluse" — both
|
|
45
|
+
// map to the same routing logic.
|
|
47
46
|
const EVENT_ALIASES = {
|
|
48
47
|
beforetool: "pretooluse",
|
|
49
48
|
before_tool: "pretooluse",
|
package/dist/server.js
CHANGED
|
@@ -9393,7 +9393,7 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
9393
9393
|
// src/server.ts
|
|
9394
9394
|
import { mkdir, realpath as realpath2 } from "node:fs/promises";
|
|
9395
9395
|
import { homedir as homedir2 } from "node:os";
|
|
9396
|
-
import { delimiter, join } from "node:path";
|
|
9396
|
+
import { delimiter, join as join2 } from "node:path";
|
|
9397
9397
|
|
|
9398
9398
|
// node_modules/zod/v3/external.js
|
|
9399
9399
|
var exports_external = {};
|
|
@@ -22353,7 +22353,7 @@ class StdioServerTransport {
|
|
|
22353
22353
|
// package.json
|
|
22354
22354
|
var package_default = {
|
|
22355
22355
|
name: "trueline-mcp",
|
|
22356
|
-
version: "2.0
|
|
22356
|
+
version: "2.1.0",
|
|
22357
22357
|
type: "module",
|
|
22358
22358
|
description: "Truth-verified file editing for AI coding agents via MCP",
|
|
22359
22359
|
license: "Apache-2.0",
|
|
@@ -23905,9 +23905,6 @@ var LANGUAGES = {
|
|
|
23905
23905
|
function getLanguageConfig(ext) {
|
|
23906
23906
|
return LANGUAGES[ext];
|
|
23907
23907
|
}
|
|
23908
|
-
function supportedExtensions() {
|
|
23909
|
-
return Object.keys(LANGUAGES);
|
|
23910
|
-
}
|
|
23911
23908
|
|
|
23912
23909
|
// src/tools/outline.ts
|
|
23913
23910
|
async function handleOutline(params) {
|
|
@@ -23918,7 +23915,7 @@ async function handleOutline(params) {
|
|
|
23918
23915
|
const ext = extname(validated.resolvedPath).toLowerCase();
|
|
23919
23916
|
const config2 = getLanguageConfig(ext);
|
|
23920
23917
|
if (!config2) {
|
|
23921
|
-
return
|
|
23918
|
+
return textResult(`No outline support for "${ext}" files — use trueline_read to read this file directly.`);
|
|
23922
23919
|
}
|
|
23923
23920
|
let source;
|
|
23924
23921
|
try {
|
|
@@ -23939,6 +23936,75 @@ async function handleOutline(params) {
|
|
|
23939
23936
|
}
|
|
23940
23937
|
}
|
|
23941
23938
|
|
|
23939
|
+
// src/update-check.ts
|
|
23940
|
+
import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
23941
|
+
import { join } from "node:path";
|
|
23942
|
+
import { tmpdir } from "node:os";
|
|
23943
|
+
var PACKAGE_NAME = "trueline-mcp";
|
|
23944
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
23945
|
+
var CACHE_FILE = join(tmpdir(), "trueline-mcp-update-check.json");
|
|
23946
|
+
var REGISTRY_TIMEOUT_MS = 3000;
|
|
23947
|
+
async function readCache() {
|
|
23948
|
+
try {
|
|
23949
|
+
const raw = await readFile2(CACHE_FILE, "utf-8");
|
|
23950
|
+
return JSON.parse(raw);
|
|
23951
|
+
} catch {
|
|
23952
|
+
return null;
|
|
23953
|
+
}
|
|
23954
|
+
}
|
|
23955
|
+
async function writeCache(entry) {
|
|
23956
|
+
await writeFile(CACHE_FILE, JSON.stringify(entry)).catch(() => {});
|
|
23957
|
+
}
|
|
23958
|
+
async function fetchLatestVersion() {
|
|
23959
|
+
try {
|
|
23960
|
+
const controller = new AbortController;
|
|
23961
|
+
const timeout = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
|
|
23962
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
23963
|
+
signal: controller.signal
|
|
23964
|
+
});
|
|
23965
|
+
clearTimeout(timeout);
|
|
23966
|
+
if (!res.ok)
|
|
23967
|
+
return null;
|
|
23968
|
+
const data = await res.json();
|
|
23969
|
+
return data.version ?? null;
|
|
23970
|
+
} catch {
|
|
23971
|
+
return null;
|
|
23972
|
+
}
|
|
23973
|
+
}
|
|
23974
|
+
function scheduleUpdateCheck(currentVersion, onUpdate) {
|
|
23975
|
+
const notify = onUpdate ?? defaultNotify;
|
|
23976
|
+
(async () => {
|
|
23977
|
+
const cached2 = await readCache();
|
|
23978
|
+
if (cached2 && Date.now() - cached2.timestamp < CHECK_INTERVAL_MS) {
|
|
23979
|
+
if (compareVersions(cached2.latestVersion, currentVersion) > 0) {
|
|
23980
|
+
notify({ current: currentVersion, latest: cached2.latestVersion });
|
|
23981
|
+
}
|
|
23982
|
+
return;
|
|
23983
|
+
}
|
|
23984
|
+
const latest = await fetchLatestVersion();
|
|
23985
|
+
if (!latest)
|
|
23986
|
+
return;
|
|
23987
|
+
await writeCache({ timestamp: Date.now(), latestVersion: latest });
|
|
23988
|
+
if (compareVersions(latest, currentVersion) > 0) {
|
|
23989
|
+
notify({ current: currentVersion, latest });
|
|
23990
|
+
}
|
|
23991
|
+
})();
|
|
23992
|
+
}
|
|
23993
|
+
function defaultNotify({ current, latest }) {
|
|
23994
|
+
process.stderr.write(`[trueline-mcp] update available: ${current} → ${latest} (npm i -g trueline-mcp)
|
|
23995
|
+
`);
|
|
23996
|
+
}
|
|
23997
|
+
function compareVersions(a, b) {
|
|
23998
|
+
const pa = a.split(".").map(Number);
|
|
23999
|
+
const pb = b.split(".").map(Number);
|
|
24000
|
+
for (let i2 = 0;i2 < Math.max(pa.length, pb.length); i2++) {
|
|
24001
|
+
const diff = (pa[i2] ?? 0) - (pb[i2] ?? 0);
|
|
24002
|
+
if (diff !== 0)
|
|
24003
|
+
return diff;
|
|
24004
|
+
}
|
|
24005
|
+
return 0;
|
|
24006
|
+
}
|
|
24007
|
+
|
|
23942
24008
|
// src/server.ts
|
|
23943
24009
|
var VERSION2 = package_default.version;
|
|
23944
24010
|
var server = new McpServer({
|
|
@@ -23949,11 +24015,13 @@ var rawProjectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
|
23949
24015
|
var projectDir = await realpath2(rawProjectDir).catch(() => rawProjectDir);
|
|
23950
24016
|
async function resolveAllowedDirs() {
|
|
23951
24017
|
const dirs = [];
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
24018
|
+
if (process.env.CLAUDE_PROJECT_DIR) {
|
|
24019
|
+
const claudeDir = join2(homedir2(), ".claude");
|
|
24020
|
+
await mkdir(claudeDir, { recursive: true }).catch(() => {});
|
|
24021
|
+
const realClaudeDir = await realpath2(claudeDir).catch(() => null);
|
|
24022
|
+
if (realClaudeDir)
|
|
24023
|
+
dirs.push(realClaudeDir);
|
|
24024
|
+
}
|
|
23957
24025
|
const extra = process.env.TRUELINE_ALLOWED_DIRS;
|
|
23958
24026
|
if (extra) {
|
|
23959
24027
|
for (const raw of extra.split(delimiter).filter(Boolean)) {
|
|
@@ -23969,10 +24037,10 @@ server.registerTool("trueline_read", {
|
|
|
23969
24037
|
description: "Read a file; returns N:hash|content per line plus a checksum per range.",
|
|
23970
24038
|
inputSchema: exports_external.object({
|
|
23971
24039
|
file_path: exports_external.string(),
|
|
23972
|
-
ranges: exports_external.array(exports_external.object({
|
|
23973
|
-
start: exports_external.number().int().positive().optional(),
|
|
23974
|
-
end: exports_external.number().int().positive().optional()
|
|
23975
|
-
})).optional(),
|
|
24040
|
+
ranges: exports_external.preprocess((val) => typeof val === "string" ? JSON.parse(val) : val, exports_external.array(exports_external.object({
|
|
24041
|
+
start: exports_external.number().int().positive().describe("First line to read (1-based).").optional(),
|
|
24042
|
+
end: exports_external.number().int().positive().describe("Last line to read (1-based, inclusive).").optional()
|
|
24043
|
+
})).describe("Line ranges to read. Omit to read the whole file. Example: [{start: 10, end: 25}] or [{start: 1, end: 50}, {start: 200, end: 220}] for disjoint ranges. Each range gets its own checksum.")).optional(),
|
|
23976
24044
|
encoding: exports_external.string().describe("File encoding. Defaults to utf-8. Supported: utf-8, ascii, latin1.").optional()
|
|
23977
24045
|
})
|
|
23978
24046
|
}, async (params) => {
|
|
@@ -23982,11 +24050,11 @@ server.registerTool("trueline_edit", {
|
|
|
23982
24050
|
description: "Apply hash-verified edits to a file. Each edit carries its own checksum.",
|
|
23983
24051
|
inputSchema: exports_external.object({
|
|
23984
24052
|
file_path: exports_external.string(),
|
|
23985
|
-
edits: exports_external.array(exports_external.object({
|
|
24053
|
+
edits: exports_external.preprocess((val) => typeof val === "string" ? JSON.parse(val) : val, exports_external.array(exports_external.object({
|
|
23986
24054
|
checksum: exports_external.string().describe("Checksum from trueline_read for the covering range"),
|
|
23987
24055
|
range: exports_external.string().describe("startLine:hash..endLine:hash or startLine:hash; prefix + for insert-after"),
|
|
23988
24056
|
content: exports_external.string().describe("Replacement lines, newline-separated. Empty string to delete.")
|
|
23989
|
-
})).min(1),
|
|
24057
|
+
})).min(1)),
|
|
23990
24058
|
encoding: exports_external.string().describe("File encoding. Defaults to utf-8. Supported: utf-8, ascii, latin1.").optional()
|
|
23991
24059
|
})
|
|
23992
24060
|
}, async (params) => {
|
|
@@ -23996,11 +24064,11 @@ server.registerTool("trueline_diff", {
|
|
|
23996
24064
|
description: "Preview edits as a unified diff without writing to disk. Each edit carries its own checksum.",
|
|
23997
24065
|
inputSchema: exports_external.object({
|
|
23998
24066
|
file_path: exports_external.string(),
|
|
23999
|
-
edits: exports_external.array(exports_external.object({
|
|
24067
|
+
edits: exports_external.preprocess((val) => typeof val === "string" ? JSON.parse(val) : val, exports_external.array(exports_external.object({
|
|
24000
24068
|
checksum: exports_external.string().describe("Checksum from trueline_read for the covering range"),
|
|
24001
24069
|
range: exports_external.string().describe("startLine:hash..endLine:hash or startLine:hash; prefix + for insert-after"),
|
|
24002
24070
|
content: exports_external.string().describe("Replacement lines, newline-separated. Empty string to delete.")
|
|
24003
|
-
})).min(1),
|
|
24071
|
+
})).min(1)),
|
|
24004
24072
|
encoding: exports_external.string().describe("File encoding. Defaults to utf-8. Supported: utf-8, ascii, latin1.").optional()
|
|
24005
24073
|
})
|
|
24006
24074
|
}, async (params) => {
|
|
@@ -24021,3 +24089,9 @@ try {
|
|
|
24021
24089
|
console.error("Failed to start trueline-mcp server:", err2);
|
|
24022
24090
|
process.exit(1);
|
|
24023
24091
|
}
|
|
24092
|
+
scheduleUpdateCheck(VERSION2, ({ current, latest }) => {
|
|
24093
|
+
const message = `update available: ${current} → ${latest} (npm i -g trueline-mcp)`;
|
|
24094
|
+
process.stderr.write(`[trueline-mcp] ${message}
|
|
24095
|
+
`);
|
|
24096
|
+
server.sendLoggingMessage({ level: "warning", logger: "trueline-mcp", data: message }).catch(() => {});
|
|
24097
|
+
});
|
package/hooks/core/access.js
CHANGED
|
@@ -30,9 +30,13 @@ export async function createAccessChecker(projectDir) {
|
|
|
30
30
|
|
|
31
31
|
// Build allowed dirs list (same logic as server.ts).
|
|
32
32
|
const allowedBases = [realBase];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
|
|
34
|
+
// ~/.claude/ — only relevant when running under Claude Code.
|
|
35
|
+
if (process.env.CLAUDE_PROJECT_DIR) {
|
|
36
|
+
try {
|
|
37
|
+
allowedBases.push(await realpath(resolve(homedir(), ".claude")));
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
const extraDirs = process.env.TRUELINE_ALLOWED_DIRS;
|
|
38
42
|
if (extraDirs) {
|
package/hooks/core/platform.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
const PLATFORM_ENV_VARS = {
|
|
10
10
|
"gemini-cli": "GEMINI_PROJECT_DIR",
|
|
11
|
-
opencode: "OPENCODE_PROJECT_DIR",
|
|
12
11
|
// claude-code and vscode-copilot both use CLAUDE_PROJECT_DIR, so we can't
|
|
13
12
|
// distinguish them by env var alone. VS Code Copilot detection would need
|
|
14
13
|
// an additional signal (e.g. VSCODE_PID). For now, both default to
|
|
@@ -26,7 +25,6 @@ export function detectPlatform() {
|
|
|
26
25
|
|
|
27
26
|
// Check unique env vars first, then fall back to shared ones.
|
|
28
27
|
if (process.env.GEMINI_PROJECT_DIR) return "gemini-cli";
|
|
29
|
-
if (process.env.OPENCODE_PROJECT_DIR) return "opencode";
|
|
30
28
|
if (process.env.CLAUDE_PROJECT_DIR) return "claude-code";
|
|
31
29
|
|
|
32
30
|
return "claude-code";
|