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 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 and
177
- `~/.claude/`. To allow additional directories, set `TRUELINE_ALLOWED_DIRS` to
178
- a colon-separated list of paths (semicolon-separated on Windows).
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
- and `~/.claude/`. To allow additional directories, set
176
- `TRUELINE_ALLOWED_DIRS` to a colon-separated list of paths
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
@@ -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: claude-code, gemini-cli, vscode-copilot
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 claude-code session-start`;
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", Claude Code calls it "pretooluse" — both map
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.5",
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 errorResult(`No outline support for "${ext}" files. Supported: ${supportedExtensions().join(", ")}`);
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
- const claudeDir = join(homedir2(), ".claude");
23953
- await mkdir(claudeDir, { recursive: true }).catch(() => {});
23954
- const realClaudeDir = await realpath2(claudeDir).catch(() => null);
23955
- if (realClaudeDir)
23956
- dirs.push(realClaudeDir);
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
+ });
@@ -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
- try {
34
- allowedBases.push(await realpath(resolve(homedir(), ".claude")));
35
- } catch {}
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) {
@@ -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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trueline-mcp",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Truth-verified file editing for AI coding agents via MCP",
6
6
  "license": "Apache-2.0",