@j0hanz/filesystem-mcp 2.8.0 → 2.8.1

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.
Files changed (75) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +2 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/cursor.d.ts +8 -0
  5. package/dist/core/cursor.d.ts.map +1 -1
  6. package/dist/core/cursor.js +14 -0
  7. package/dist/core/cursor.js.map +1 -1
  8. package/dist/core/fs.d.ts.map +1 -1
  9. package/dist/core/fs.js +64 -40
  10. package/dist/core/fs.js.map +1 -1
  11. package/dist/core/input-required.d.ts +7 -0
  12. package/dist/core/input-required.d.ts.map +1 -1
  13. package/dist/core/input-required.js +13 -0
  14. package/dist/core/input-required.js.map +1 -1
  15. package/dist/core/path-utils.d.ts +9 -6
  16. package/dist/core/path-utils.d.ts.map +1 -1
  17. package/dist/core/path-utils.js +13 -19
  18. package/dist/core/path-utils.js.map +1 -1
  19. package/dist/core/path.d.ts +1 -18
  20. package/dist/core/path.d.ts.map +1 -1
  21. package/dist/core/path.js +5 -40
  22. package/dist/core/path.js.map +1 -1
  23. package/dist/core/read.d.ts +7 -3
  24. package/dist/core/read.d.ts.map +1 -1
  25. package/dist/core/read.js +20 -37
  26. package/dist/core/read.js.map +1 -1
  27. package/dist/core/sensitive.d.ts.map +1 -1
  28. package/dist/core/sensitive.js +7 -14
  29. package/dist/core/sensitive.js.map +1 -1
  30. package/dist/core/util.d.ts +6 -0
  31. package/dist/core/util.d.ts.map +1 -1
  32. package/dist/core/util.js +16 -0
  33. package/dist/core/util.js.map +1 -1
  34. package/dist/prompts.d.ts +1 -1
  35. package/dist/prompts.d.ts.map +1 -1
  36. package/dist/prompts.js +1 -1
  37. package/dist/prompts.js.map +1 -1
  38. package/dist/resources.d.ts +1 -2
  39. package/dist/resources.d.ts.map +1 -1
  40. package/dist/resources.js +2 -4
  41. package/dist/resources.js.map +1 -1
  42. package/dist/server.js +1 -1
  43. package/dist/server.js.map +1 -1
  44. package/dist/tools/create.d.ts.map +1 -1
  45. package/dist/tools/create.js +5 -6
  46. package/dist/tools/create.js.map +1 -1
  47. package/dist/tools/delete.d.ts.map +1 -1
  48. package/dist/tools/delete.js +7 -6
  49. package/dist/tools/delete.js.map +1 -1
  50. package/dist/tools/find-files.d.ts.map +1 -1
  51. package/dist/tools/find-files.js +6 -12
  52. package/dist/tools/find-files.js.map +1 -1
  53. package/dist/tools/index.d.ts +1 -1
  54. package/dist/tools/index.d.ts.map +1 -1
  55. package/dist/tools/index.js +1 -1
  56. package/dist/tools/index.js.map +1 -1
  57. package/dist/tools/list.d.ts.map +1 -1
  58. package/dist/tools/list.js +30 -13
  59. package/dist/tools/list.js.map +1 -1
  60. package/dist/tools/move.d.ts.map +1 -1
  61. package/dist/tools/move.js +12 -13
  62. package/dist/tools/move.js.map +1 -1
  63. package/dist/tools/replace-text.js +1 -1
  64. package/dist/tools/replace-text.js.map +1 -1
  65. package/dist/tools/search-text.d.ts.map +1 -1
  66. package/dist/tools/search-text.js +7 -18
  67. package/dist/tools/search-text.js.map +1 -1
  68. package/dist/transport/http-policy.d.ts +3 -2
  69. package/dist/transport/http-policy.d.ts.map +1 -1
  70. package/dist/transport/http-policy.js +3 -2
  71. package/dist/transport/http-policy.js.map +1 -1
  72. package/dist/transport/http.d.ts.map +1 -1
  73. package/dist/transport/http.js +1 -2
  74. package/dist/transport/http.js.map +1 -1
  75. package/package.json +1 -1
@@ -1,11 +1,10 @@
1
1
  import { basename, posix } from 'node:path';
2
2
  import * as z from 'zod/v4';
3
- import { paginate } from "../core/cursor.js";
4
- import { ErrorCode } from "../core/errors.js";
3
+ import { pageQueryKey, paginate } from "../core/cursor.js";
4
+ import { ErrorCode, isFsError, isSkippableErrno, SKIPPABLE_FS_CODES } from "../core/errors.js";
5
5
  import { pageTrailer } from "../core/fmt.js";
6
6
  import { globEntries } from "../core/glob.js";
7
- import { resolveEntryType } from "../core/path-utils.js";
8
- import { toPosixRelative } from "../core/path.js";
7
+ import { resolveEntryType, toPosixRelative } from "../core/path-utils.js";
9
8
  import { CursorSchema, IncludeHidden, IncludeIgnored, OptionalPath, PositiveInt, } from "../core/schema.js";
10
9
  import { putJsonResource } from "../core/store.js";
11
10
  import { DEFAULT_SEARCH_TIMEOUT_MS, DEFAULT_TREE_ENTRIES, MAX_LIST_ENTRIES, MAX_TREE_DEPTH, } from "../core/util.js";
@@ -29,6 +28,31 @@ function compareEntries(a, b) {
29
28
  return rankDiff;
30
29
  return a.name.localeCompare(b.name);
31
30
  }
31
+ /**
32
+ * True when a walked entry is inside the allowed directories and not sensitive,
33
+ * on both its requested and resolved paths. Every entry is realpath-resolved: a
34
+ * symlinked ancestor pointing outside the sandbox would otherwise pass the
35
+ * lexical check (fs.glob follows symlinks and yields external entries as
36
+ * non-symlink dirents). Escapes and sensitive targets throw ACCESS_DENIED and
37
+ * skippable errors filter the entry; anything else is fatal. The accepted
38
+ * TOCTOU window is documented on PathGuard.
39
+ */
40
+ async function isEntryAccessible(guard, entryPath) {
41
+ try {
42
+ const { requestedPath, resolvedPath } = await guard.validateExistingPathDetailed(entryPath);
43
+ return !guard.isSensitive(requestedPath) && !guard.isSensitive(resolvedPath);
44
+ }
45
+ catch (error) {
46
+ if (isFsError(error)) {
47
+ if (SKIPPABLE_FS_CODES.has(error.code))
48
+ return false;
49
+ throw error;
50
+ }
51
+ if (isSkippableErrno(error))
52
+ return false;
53
+ throw error;
54
+ }
55
+ }
32
56
  async function collect(rootPath, options) {
33
57
  const entries = [];
34
58
  let scanned = 0;
@@ -56,7 +80,7 @@ async function collect(rootPath, options) {
56
80
  const entryType = resolveEntryType(entry.dirent);
57
81
  const relPath = toPosixRelative(rootPath, entry.path);
58
82
  const name = basename(relPath);
59
- const accessible = await options.pathGuard.isEntryAccessible(entry.path);
83
+ const accessible = await isEntryAccessible(options.pathGuard, entry.path);
60
84
  if (!accessible)
61
85
  continue;
62
86
  totalEntries++;
@@ -163,16 +187,9 @@ export const LIST = defineTool({
163
187
  accessPaths: (args) => (args.path ? [args.path] : []),
164
188
  run: async (args, ctx) => {
165
189
  const resolvedPath = ctx.fs.pathGuard.resolvePathOrRoot(args.path);
166
- const queryKey = JSON.stringify({
167
- method: 'list',
168
- path: resolvedPath,
169
- maxDepth: args.maxDepth,
170
- includeHidden: args.includeHidden,
171
- includeIgnored: args.includeIgnored,
172
- });
173
190
  const paged = await paginate({
174
191
  store: ctx.pageStore,
175
- queryKey,
192
+ queryKey: pageQueryKey('list', { ...args, path: resolvedPath }, ['cursor', 'maxEntries']),
176
193
  cursor: args.cursor,
177
194
  pageSize: args.maxEntries,
178
195
  produce: async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/tools/list.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, posix } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { paginate } from '../core/cursor.ts';\nimport { ErrorCode } from '../core/errors.ts';\nimport { pageTrailer } from '../core/fmt.ts';\nimport { globEntries } from '../core/glob.ts';\nimport { type EntryType, resolveEntryType } from '../core/path-utils.ts';\nimport type { PathGuard } from '../core/path.ts';\nimport { toPosixRelative } from '../core/path.ts';\nimport {\n CursorSchema,\n IncludeHidden,\n IncludeIgnored,\n OptionalPath,\n PositiveInt,\n} from '../core/schema.ts';\nimport type { JsonResourceResult } from '../core/store.ts';\nimport { putJsonResource } from '../core/store.ts';\nimport {\n DEFAULT_SEARCH_TIMEOUT_MS,\n DEFAULT_TREE_ENTRIES,\n MAX_LIST_ENTRIES,\n MAX_TREE_DEPTH,\n} from '../core/util.ts';\nimport { defineTool } from './define.ts';\n\ninterface CollectedEntry {\n name: string;\n relativePath: string; // POSIX\n type: EntryType;\n}\n\ninterface CollectOptions {\n maxDepth: number;\n includeHidden: boolean;\n includeIgnored: boolean;\n signal: AbortSignal;\n pathGuard: PathGuard;\n /** Upper bound on the stored, paginable entry array. */\n entryCap: number;\n onProgress: (progress: { current: number; total?: number }) => void;\n}\n\ninterface CollectResult {\n entries: CollectedEntry[];\n totalEntries: number;\n totalFiles: number;\n totalDirectories: number;\n}\n\nfunction entryTypeRank(type: EntryType): number {\n if (type === 'directory') return 0;\n return 1;\n}\n\nfunction compareEntries(a: CollectedEntry, b: CollectedEntry): number {\n // Compare by parent directory first (depth-level grouping)\n const slashA = a.relativePath.lastIndexOf('/');\n const slashB = b.relativePath.lastIndexOf('/');\n const parentA = slashA === -1 ? '' : a.relativePath.slice(0, slashA);\n const parentB = slashB === -1 ? '' : b.relativePath.slice(0, slashB);\n\n if (parentA !== parentB) return parentA.localeCompare(parentB);\n\n // Within same parent: dirs first, then alphabetical\n const rankDiff = entryTypeRank(a.type) - entryTypeRank(b.type);\n if (rankDiff !== 0) return rankDiff;\n return a.name.localeCompare(b.name);\n}\n\nasync function collect(rootPath: string, options: CollectOptions): Promise<CollectResult> {\n const entries: CollectedEntry[] = [];\n let scanned = 0;\n let totalEntries = 0;\n let totalFiles = 0;\n let totalDirectories = 0;\n const ceiling = options.pathGuard.allowedRootContaining(rootPath);\n\n for await (const entry of globEntries({\n cwd: rootPath,\n pattern: '**/*',\n skipIgnored: !options.includeIgnored,\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n signal: options.signal,\n includeHidden: options.includeHidden,\n baseNameMatch: false,\n // ListInputSchema.maxDepth is 1-based (1 = top-level only); the shared\n // globEntries primitive is now 0-based, so subtract one here. options.maxDepth\n // is guaranteed >= 1 by its PositiveInt schema, so this never underflows.\n maxDepth: options.maxDepth - 1,\n onlyFiles: false,\n })) {\n options.signal.throwIfAborted();\n scanned++;\n options.onProgress({ current: scanned });\n\n const entryType: EntryType = resolveEntryType(entry.dirent);\n const relPath = toPosixRelative(rootPath, entry.path);\n const name = basename(relPath);\n\n const accessible = await options.pathGuard.isEntryAccessible(entry.path);\n if (!accessible) continue;\n\n totalEntries++;\n if (entryType === 'directory') {\n totalDirectories++;\n } else {\n totalFiles++;\n }\n\n const collectedEntry: CollectedEntry = {\n name,\n relativePath: relPath,\n type: entryType,\n };\n\n if (entries.length < options.entryCap) {\n entries.push(collectedEntry);\n }\n }\n\n options.signal.throwIfAborted();\n entries.sort(compareEntries);\n\n return {\n entries,\n totalEntries,\n totalFiles,\n totalDirectories,\n };\n}\n\nconst TEE = '├── ';\nconst ELBOW = '└── ';\nconst PIPE = '│ ';\nconst INDENT = ' ';\n\nfunction renderMarkdown(rootName: string, page: readonly CollectedEntry[]): string {\n if (page.length === 0) return rootName;\n\n // The tree is drawn down from the root, but a later page (or a capped set)\n // can hold entries whose ancestors sat elsewhere; those would never be drawn.\n // Re-draw each missing ancestor as context so every entry stays locatable.\n const present = new Set(page.map((entry) => entry.relativePath));\n const entries = [...page];\n for (const entry of page) {\n for (\n let dir = posix.dirname(entry.relativePath);\n dir !== '.' && !present.has(dir);\n dir = posix.dirname(dir)\n ) {\n present.add(dir);\n entries.push({ name: posix.basename(dir), relativePath: dir, type: 'directory' });\n }\n }\n entries.sort(compareEntries);\n\n // Group entries by parent path\n const childrenOf = new Map<string, CollectedEntry[]>();\n for (const entry of entries) {\n const slash = entry.relativePath.lastIndexOf('/');\n const parentKey = slash === -1 ? '' : entry.relativePath.slice(0, slash);\n if (!childrenOf.has(parentKey)) childrenOf.set(parentKey, []);\n const children = childrenOf.get(parentKey);\n if (children) children.push(entry);\n }\n\n const lines: string[] = [rootName];\n\n function renderChildren(parentKey: string, prefix: string): void {\n const children = childrenOf.get(parentKey);\n if (!children) return;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (!child) continue;\n const isLast = i === children.length - 1;\n const connector = isLast ? ELBOW : TEE;\n lines.push(prefix + connector + child.name + (child.type === 'directory' ? '/' : ''));\n\n const childKey = child.relativePath;\n const nextPrefix = prefix + (isLast ? INDENT : PIPE);\n renderChildren(childKey, nextPrefix);\n }\n }\n\n renderChildren('', '');\n return lines.join('\\n');\n}\n\nconst DEFAULT_LIST_DEPTH = 1;\n\nconst ListInputSchema = z.strictObject({\n path: OptionalPath.describe('Directory to list (default: first allowed root)'),\n maxDepth: PositiveInt.max(MAX_TREE_DEPTH)\n .default(DEFAULT_LIST_DEPTH)\n .describe(\n `Max directory depth to traverse (default: ${String(DEFAULT_LIST_DEPTH)} = top-level only; increase to recurse deeper)`,\n ),\n maxEntries: PositiveInt.max(MAX_LIST_ENTRIES)\n .default(DEFAULT_TREE_ENTRIES)\n .describe(\n `Page size (default: ${String(DEFAULT_TREE_ENTRIES)}). Continue with nextCursor; an incomplete first page also carries resourceUri for the whole list.`,\n ),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n cursor: CursorSchema,\n});\n\ninterface ListPageMetadata {\n readonly path: string;\n readonly totalEntries: number;\n readonly totalFiles: number;\n readonly totalDirectories: number;\n}\n\ntype ListOutput = ListPageMetadata & {\n entries: CollectedEntry[];\n entryCount: number;\n resourceUri?: string;\n nextCursor?: string;\n};\n\nexport const LIST = defineTool<typeof ListInputSchema, ListOutput>({\n name: 'list',\n title: 'List',\n description:\n \"List a directory's files and subdirectories as a tree of names, like ls or tree. \" +\n 'stat returns sizes and dates.',\n input: ListInputSchema,\n readOnlyHint: true,\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.NOT_DIRECTORY,\n progress: (args) => ({\n label: 'List',\n subject: args.path ? basename(args.path) : '.',\n }),\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const resolvedPath = ctx.fs.pathGuard.resolvePathOrRoot(args.path);\n const queryKey = JSON.stringify({\n method: 'list',\n path: resolvedPath,\n maxDepth: args.maxDepth,\n includeHidden: args.includeHidden,\n includeIgnored: args.includeIgnored,\n });\n const paged = await paginate<CollectedEntry, ListPageMetadata, JsonResourceResult>({\n store: ctx.pageStore,\n queryKey,\n cursor: args.cursor,\n pageSize: args.maxEntries,\n produce: async () => {\n const validDir = await ctx.fs.pathGuard.validateExistingDirectory(resolvedPath);\n const result = await collect(validDir, {\n maxDepth: args.maxDepth,\n includeHidden: args.includeHidden,\n includeIgnored: args.includeIgnored,\n signal: ctx.signal,\n pathGuard: ctx.fs.pathGuard,\n entryCap: MAX_LIST_ENTRIES,\n onProgress: ctx.onProgress,\n });\n return {\n items: result.entries,\n metadata: {\n path: validDir,\n totalEntries: result.totalEntries,\n totalFiles: result.totalFiles,\n totalDirectories: result.totalDirectories,\n },\n truncated: result.totalEntries > result.entries.length,\n };\n },\n externalize: (entries, metadata) => {\n const name = basename(metadata.path);\n // The stored tree is the whole collected set, bounded by the hard cap.\n const fullOutput = {\n entries,\n markdown: renderMarkdown(name, entries),\n totalEntries: metadata.totalEntries,\n totalFiles: metadata.totalFiles,\n totalDirectories: metadata.totalDirectories,\n ...(metadata.totalEntries > entries.length ? { truncated: true } : {}),\n };\n return putJsonResource(ctx.resourceStore, `${name} tree`, fullOutput);\n },\n });\n\n const structured: ListOutput = {\n ...paged.metadata,\n entries: [...paged.page],\n entryCount: paged.page.length,\n ...(paged.resource ? { resourceUri: paged.resource.entry.uri } : {}),\n ...(paged.nextCursor !== undefined ? { nextCursor: paged.nextCursor } : {}),\n };\n // The tree is what the model reads, so position and the full-list URI ride\n // the text too — paging needs no second lookup. `pageTrailer` is the one\n // owner of that line, and it owes it on the last page as much as the first.\n const text =\n renderMarkdown(basename(paged.metadata.path), [...paged.page]) +\n pageTrailer({\n offset: paged.offset,\n shown: structured.entryCount,\n total: structured.totalEntries,\n noun: 'entries',\n tool: 'list',\n nextCursor: structured.nextCursor,\n nextArgs: args,\n }) +\n (structured.resourceUri !== undefined ? `\\nfull tree at ${structured.resourceUri}` : '');\n return {\n structured,\n text,\n ...(paged.resource ? { resources: [paged.resource.link] } : {}),\n };\n },\n});\n"]}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/tools/list.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, posix } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { pageQueryKey, paginate } from '../core/cursor.ts';\nimport { ErrorCode, isFsError, isSkippableErrno, SKIPPABLE_FS_CODES } from '../core/errors.ts';\nimport { pageTrailer } from '../core/fmt.ts';\nimport { globEntries } from '../core/glob.ts';\nimport { type EntryType, resolveEntryType, toPosixRelative } from '../core/path-utils.ts';\nimport type { PathGuard } from '../core/path.ts';\nimport {\n CursorSchema,\n IncludeHidden,\n IncludeIgnored,\n OptionalPath,\n PositiveInt,\n} from '../core/schema.ts';\nimport type { JsonResourceResult } from '../core/store.ts';\nimport { putJsonResource } from '../core/store.ts';\nimport {\n DEFAULT_SEARCH_TIMEOUT_MS,\n DEFAULT_TREE_ENTRIES,\n MAX_LIST_ENTRIES,\n MAX_TREE_DEPTH,\n} from '../core/util.ts';\nimport { defineTool } from './define.ts';\n\ninterface CollectedEntry {\n name: string;\n relativePath: string; // POSIX\n type: EntryType;\n}\n\ninterface CollectOptions {\n maxDepth: number;\n includeHidden: boolean;\n includeIgnored: boolean;\n signal: AbortSignal;\n pathGuard: PathGuard;\n /** Upper bound on the stored, paginable entry array. */\n entryCap: number;\n onProgress: (progress: { current: number; total?: number }) => void;\n}\n\ninterface CollectResult {\n entries: CollectedEntry[];\n totalEntries: number;\n totalFiles: number;\n totalDirectories: number;\n}\n\nfunction entryTypeRank(type: EntryType): number {\n if (type === 'directory') return 0;\n return 1;\n}\n\nfunction compareEntries(a: CollectedEntry, b: CollectedEntry): number {\n // Compare by parent directory first (depth-level grouping)\n const slashA = a.relativePath.lastIndexOf('/');\n const slashB = b.relativePath.lastIndexOf('/');\n const parentA = slashA === -1 ? '' : a.relativePath.slice(0, slashA);\n const parentB = slashB === -1 ? '' : b.relativePath.slice(0, slashB);\n\n if (parentA !== parentB) return parentA.localeCompare(parentB);\n\n // Within same parent: dirs first, then alphabetical\n const rankDiff = entryTypeRank(a.type) - entryTypeRank(b.type);\n if (rankDiff !== 0) return rankDiff;\n return a.name.localeCompare(b.name);\n}\n\n/**\n * True when a walked entry is inside the allowed directories and not sensitive,\n * on both its requested and resolved paths. Every entry is realpath-resolved: a\n * symlinked ancestor pointing outside the sandbox would otherwise pass the\n * lexical check (fs.glob follows symlinks and yields external entries as\n * non-symlink dirents). Escapes and sensitive targets throw ACCESS_DENIED and\n * skippable errors filter the entry; anything else is fatal. The accepted\n * TOCTOU window is documented on PathGuard.\n */\nasync function isEntryAccessible(guard: PathGuard, entryPath: string): Promise<boolean> {\n try {\n const { requestedPath, resolvedPath } = await guard.validateExistingPathDetailed(entryPath);\n return !guard.isSensitive(requestedPath) && !guard.isSensitive(resolvedPath);\n } catch (error) {\n if (isFsError(error)) {\n if (SKIPPABLE_FS_CODES.has(error.code)) return false;\n throw error;\n }\n if (isSkippableErrno(error)) return false;\n throw error;\n }\n}\n\nasync function collect(rootPath: string, options: CollectOptions): Promise<CollectResult> {\n const entries: CollectedEntry[] = [];\n let scanned = 0;\n let totalEntries = 0;\n let totalFiles = 0;\n let totalDirectories = 0;\n const ceiling = options.pathGuard.allowedRootContaining(rootPath);\n\n for await (const entry of globEntries({\n cwd: rootPath,\n pattern: '**/*',\n skipIgnored: !options.includeIgnored,\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n signal: options.signal,\n includeHidden: options.includeHidden,\n baseNameMatch: false,\n // ListInputSchema.maxDepth is 1-based (1 = top-level only); the shared\n // globEntries primitive is now 0-based, so subtract one here. options.maxDepth\n // is guaranteed >= 1 by its PositiveInt schema, so this never underflows.\n maxDepth: options.maxDepth - 1,\n onlyFiles: false,\n })) {\n options.signal.throwIfAborted();\n scanned++;\n options.onProgress({ current: scanned });\n\n const entryType: EntryType = resolveEntryType(entry.dirent);\n const relPath = toPosixRelative(rootPath, entry.path);\n const name = basename(relPath);\n\n const accessible = await isEntryAccessible(options.pathGuard, entry.path);\n if (!accessible) continue;\n\n totalEntries++;\n if (entryType === 'directory') {\n totalDirectories++;\n } else {\n totalFiles++;\n }\n\n const collectedEntry: CollectedEntry = {\n name,\n relativePath: relPath,\n type: entryType,\n };\n\n if (entries.length < options.entryCap) {\n entries.push(collectedEntry);\n }\n }\n\n options.signal.throwIfAborted();\n entries.sort(compareEntries);\n\n return {\n entries,\n totalEntries,\n totalFiles,\n totalDirectories,\n };\n}\n\nconst TEE = '├── ';\nconst ELBOW = '└── ';\nconst PIPE = '│ ';\nconst INDENT = ' ';\n\nfunction renderMarkdown(rootName: string, page: readonly CollectedEntry[]): string {\n if (page.length === 0) return rootName;\n\n // The tree is drawn down from the root, but a later page (or a capped set)\n // can hold entries whose ancestors sat elsewhere; those would never be drawn.\n // Re-draw each missing ancestor as context so every entry stays locatable.\n const present = new Set(page.map((entry) => entry.relativePath));\n const entries = [...page];\n for (const entry of page) {\n for (\n let dir = posix.dirname(entry.relativePath);\n dir !== '.' && !present.has(dir);\n dir = posix.dirname(dir)\n ) {\n present.add(dir);\n entries.push({ name: posix.basename(dir), relativePath: dir, type: 'directory' });\n }\n }\n entries.sort(compareEntries);\n\n // Group entries by parent path\n const childrenOf = new Map<string, CollectedEntry[]>();\n for (const entry of entries) {\n const slash = entry.relativePath.lastIndexOf('/');\n const parentKey = slash === -1 ? '' : entry.relativePath.slice(0, slash);\n if (!childrenOf.has(parentKey)) childrenOf.set(parentKey, []);\n const children = childrenOf.get(parentKey);\n if (children) children.push(entry);\n }\n\n const lines: string[] = [rootName];\n\n function renderChildren(parentKey: string, prefix: string): void {\n const children = childrenOf.get(parentKey);\n if (!children) return;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (!child) continue;\n const isLast = i === children.length - 1;\n const connector = isLast ? ELBOW : TEE;\n lines.push(prefix + connector + child.name + (child.type === 'directory' ? '/' : ''));\n\n const childKey = child.relativePath;\n const nextPrefix = prefix + (isLast ? INDENT : PIPE);\n renderChildren(childKey, nextPrefix);\n }\n }\n\n renderChildren('', '');\n return lines.join('\\n');\n}\n\nconst DEFAULT_LIST_DEPTH = 1;\n\nconst ListInputSchema = z.strictObject({\n path: OptionalPath.describe('Directory to list (default: first allowed root)'),\n maxDepth: PositiveInt.max(MAX_TREE_DEPTH)\n .default(DEFAULT_LIST_DEPTH)\n .describe(\n `Max directory depth to traverse (default: ${String(DEFAULT_LIST_DEPTH)} = top-level only; increase to recurse deeper)`,\n ),\n maxEntries: PositiveInt.max(MAX_LIST_ENTRIES)\n .default(DEFAULT_TREE_ENTRIES)\n .describe(\n `Page size (default: ${String(DEFAULT_TREE_ENTRIES)}). Continue with nextCursor; an incomplete first page also carries resourceUri for the whole list.`,\n ),\n includeHidden: IncludeHidden,\n includeIgnored: IncludeIgnored,\n cursor: CursorSchema,\n});\n\ninterface ListPageMetadata {\n readonly path: string;\n readonly totalEntries: number;\n readonly totalFiles: number;\n readonly totalDirectories: number;\n}\n\ntype ListOutput = ListPageMetadata & {\n entries: CollectedEntry[];\n entryCount: number;\n resourceUri?: string;\n nextCursor?: string;\n};\n\nexport const LIST = defineTool<typeof ListInputSchema, ListOutput>({\n name: 'list',\n title: 'List',\n description:\n \"List a directory's files and subdirectories as a tree of names, like ls or tree. \" +\n 'stat returns sizes and dates.',\n input: ListInputSchema,\n readOnlyHint: true,\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.NOT_DIRECTORY,\n progress: (args) => ({\n label: 'List',\n subject: args.path ? basename(args.path) : '.',\n }),\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const resolvedPath = ctx.fs.pathGuard.resolvePathOrRoot(args.path);\n const paged = await paginate<CollectedEntry, ListPageMetadata, JsonResourceResult>({\n store: ctx.pageStore,\n queryKey: pageQueryKey('list', { ...args, path: resolvedPath }, ['cursor', 'maxEntries']),\n cursor: args.cursor,\n pageSize: args.maxEntries,\n produce: async () => {\n const validDir = await ctx.fs.pathGuard.validateExistingDirectory(resolvedPath);\n const result = await collect(validDir, {\n maxDepth: args.maxDepth,\n includeHidden: args.includeHidden,\n includeIgnored: args.includeIgnored,\n signal: ctx.signal,\n pathGuard: ctx.fs.pathGuard,\n entryCap: MAX_LIST_ENTRIES,\n onProgress: ctx.onProgress,\n });\n return {\n items: result.entries,\n metadata: {\n path: validDir,\n totalEntries: result.totalEntries,\n totalFiles: result.totalFiles,\n totalDirectories: result.totalDirectories,\n },\n truncated: result.totalEntries > result.entries.length,\n };\n },\n externalize: (entries, metadata) => {\n const name = basename(metadata.path);\n // The stored tree is the whole collected set, bounded by the hard cap.\n const fullOutput = {\n entries,\n markdown: renderMarkdown(name, entries),\n totalEntries: metadata.totalEntries,\n totalFiles: metadata.totalFiles,\n totalDirectories: metadata.totalDirectories,\n ...(metadata.totalEntries > entries.length ? { truncated: true } : {}),\n };\n return putJsonResource(ctx.resourceStore, `${name} tree`, fullOutput);\n },\n });\n\n const structured: ListOutput = {\n ...paged.metadata,\n entries: [...paged.page],\n entryCount: paged.page.length,\n ...(paged.resource ? { resourceUri: paged.resource.entry.uri } : {}),\n ...(paged.nextCursor !== undefined ? { nextCursor: paged.nextCursor } : {}),\n };\n // The tree is what the model reads, so position and the full-list URI ride\n // the text too — paging needs no second lookup. `pageTrailer` is the one\n // owner of that line, and it owes it on the last page as much as the first.\n const text =\n renderMarkdown(basename(paged.metadata.path), [...paged.page]) +\n pageTrailer({\n offset: paged.offset,\n shown: structured.entryCount,\n total: structured.totalEntries,\n noun: 'entries',\n tool: 'list',\n nextCursor: structured.nextCursor,\n nextArgs: args,\n }) +\n (structured.resourceUri !== undefined ? `\\nfull tree at ${structured.resourceUri}` : '');\n return {\n structured,\n text,\n ...(paged.resource ? { resources: [paged.resource.link] } : {}),\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"move.d.ts","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAwfvD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,EACtD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED,eAAO,MAAM,IAAI,mCAwCf,CAAC"}
1
+ {"version":3,"file":"move.d.ts","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAsfvD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,EACtD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED,eAAO,MAAM,IAAI,mCAwCf,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { isInputRequiredResult } from '@modelcontextprotocol/server';
2
- import { stat } from 'node:fs/promises';
3
2
  import { basename, dirname, relative, resolve } from 'node:path';
4
3
  import * as z from 'zod/v4';
5
4
  import { processInParallel } from "../core/concurrency.js";
@@ -7,7 +6,7 @@ import { ErrorCode, FsError, isFsError, isNodeError, Problem, rethrowIfAborted,
7
6
  import { joinRoster, pathLabel } from "../core/fmt.js";
8
7
  import { destExists } from "../core/fs.js";
9
8
  import { globEntries } from "../core/glob.js";
10
- import { confirmKey, describeRefusal, pendingRoundTrip, readAcceptedChoice, } from "../core/input-required.js";
9
+ import { confirmKey, pendingRoundTrip, readPendingVerdict } from "../core/input-required.js";
11
10
  import { isPathInsideDirectory, isSamePath, normalizeCaseForComparison, respellCaseOnlyTarget, } from "../core/path-utils.js";
12
11
  import { defaultFalseBoolean, RequiredPath } from "../core/schema.js";
13
12
  import { PARALLEL_CONCURRENCY } from "../core/util.js";
@@ -37,9 +36,10 @@ async function planTransfer(op, pair, fs, overwrite, signal) {
37
36
  let opSource;
38
37
  let validDest;
39
38
  try {
40
- ({ realSource, opSource } = await validateTransferSource(op, pair.source, fs));
39
+ const source = await validateTransferSource(op, pair.source, fs, signal);
40
+ ({ realSource, opSource } = source);
41
41
  validDest = await fs.pathGuard.validatePathForWrite(pair.destination);
42
- if (!(op === 'move' && opSource !== realSource) && (await stat(realSource)).isDirectory()) {
42
+ if (!(op === 'move' && opSource !== realSource) && source.isDirectory) {
43
43
  await assertTreeHasNoProtectedEntries(op, realSource, pair.source, validDest, fs, signal);
44
44
  }
45
45
  }
@@ -95,13 +95,12 @@ async function planTransfer(op, pair, fs, overwrite, signal) {
95
95
  */
96
96
  async function executeTransfer(op, plan, ctx, pendingSorted) {
97
97
  if (plan.pending) {
98
- const key = confirmKey(pendingSorted.indexOf(plan.validDest));
99
- const choice = readAcceptedChoice(ctx.inputResponses, key);
100
- if (choice === 'skip') {
98
+ const verdict = readPendingVerdict(ctx, pendingSorted, plan.validDest, 'overwrite');
99
+ if (verdict === 'skipped') {
101
100
  return { skipped: plan.pair.destination };
102
101
  }
103
- if (choice !== 'overwrite') {
104
- throw new FsError(ErrorCode.CANCELLED, `${VERB[op]} cancelled: overwrite of "${plan.pair.destination}" was ${describeRefusal(ctx, key)}`, plan.pair.destination);
102
+ if (verdict !== 'accepted') {
103
+ throw new FsError(ErrorCode.CANCELLED, `${VERB[op]} cancelled: overwrite of "${plan.pair.destination}" was ${verdict.refused}`, plan.pair.destination);
105
104
  }
106
105
  }
107
106
  // TOCTOU check before any mutation: a destination that did not exist when
@@ -272,14 +271,14 @@ function buildSummary(verb, results, failures, skipped) {
272
271
  * move and renames the link over that target, destroying it. A copy reads
273
272
  * through the link, so both views are the resolved one.
274
273
  */
275
- async function validateTransferSource(op, source, fs) {
274
+ async function validateTransferSource(op, source, fs, signal) {
276
275
  try {
277
- const realSource = await fs.pathGuard.validateExistingPath(source);
276
+ const { stats, validPath: realSource } = await fs.stat(source, { signal });
278
277
  const opSource = op === 'move' ? await fs.pathGuard.validatePathForDelete(source) : realSource;
279
- return { realSource, opSource };
278
+ return { realSource, opSource, isDirectory: stats.isDirectory() };
280
279
  }
281
280
  catch (error) {
282
- if (isFsError(error))
281
+ if (isFsError(error) || signal?.aborted)
283
282
  throw error;
284
283
  throw new FsError(ErrorCode.ACCESS_DENIED, `${VERB[op]} failed for ${source}`, source);
285
284
  }
@@ -1 +1 @@
1
- {"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputRequiredResult } from '@modelcontextprotocol/server';\nimport { isInputRequiredResult } from '@modelcontextprotocol/server';\n\nimport { stat } from 'node:fs/promises';\nimport { basename, dirname, relative, resolve } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.ts';\nimport {\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.ts';\nimport { joinRoster, pathLabel } from '../core/fmt.ts';\nimport { destExists } from '../core/fs.ts';\nimport type { GuardedFileSystem } from '../core/fs.ts';\nimport { globEntries } from '../core/glob.ts';\nimport {\n confirmKey,\n describeRefusal,\n pendingRoundTrip,\n readAcceptedChoice,\n} from '../core/input-required.ts';\nimport {\n isPathInsideDirectory,\n isSamePath,\n normalizeCaseForComparison,\n respellCaseOnlyTarget,\n} from '../core/path-utils.ts';\nimport { defaultFalseBoolean, RequiredPath } from '../core/schema.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\nimport { defineTool } from './define.ts';\n\nconst MoveItemSchema = z.strictObject({\n source: RequiredPath.describe('Path of the file or directory to move or copy'),\n destination: RequiredPath.describe('Destination path'),\n});\n\ninterface MoveItemResult {\n from: string;\n to: string;\n}\n\nconst MoveInputSchema = z.strictObject({\n moves: z.array(MoveItemSchema).min(1).max(100).describe('Operations to perform (max 100)'),\n copy: defaultFalseBoolean('Copy instead of move; sources are left in place'),\n overwrite: defaultFalseBoolean(\n 'Copy mode only: overwrite existing destinations without confirmation',\n ),\n});\n\ninterface MoveFailureItem {\n source: string;\n destination: string;\n error: Problem;\n}\n\ninterface MoveOutput {\n moves: MoveItemResult[];\n failures?: MoveFailureItem[];\n skipped?: string[];\n}\n\ntype PairOp = 'move' | 'copy';\n\nconst VERB: Readonly<Record<PairOp, string>> = { move: 'Move', copy: 'Copy' };\n\n/** A move or copy pre-checked up to the point a confirmation decision is needed. */\ninterface TransferPlan {\n pair: { source: string; destination: string };\n /**\n * The path the mutation runs on: the link itself for a move, the resolved\n * target for a copy. Every collision check uses the resolved source instead\n * — see {@link validateTransferSource}.\n */\n opSource: string;\n /** Symlink-resolved source, for cross-entry overlap detection (opSource may be the link). */\n resolvedSource: string;\n validDest: string;\n /**\n * A rename that only changes the destination's case. Move must perform it —\n * it is real work — so it is exempt from the destination-exists checks that\n * would otherwise read the source itself as an existing destination. Never\n * true for a copy, which treats a case-only target as a self-copy and skips.\n */\n isCaseOnlyRename: boolean;\n /** Whether the destination existed when planned (drives the TOCTOU guard). */\n destExistedOriginally: boolean;\n /** Destination exists and is not a self/case-only target → needs overwrite confirmation. */\n pending: boolean;\n}\n\n/**\n * Phase 1 (no mutation): validate source and destination, reject self-targets\n * and transfers into a subdirectory of the source, and stat the destination to\n * decide whether this pair needs an overwrite confirmation. No `mkdir` happens\n * here — the old flow created the destination's parent before asking, which\n * mutated the filesystem before a confirmation; that now waits for phase 2 (R14).\n *\n * `overwrite` is copy-only — move has no confirmation bypass — so a move always\n * passes false.\n */\nasync function planTransfer(\n op: PairOp,\n pair: { source: string; destination: string },\n fs: ToolCtx['fs'],\n overwrite: boolean,\n signal: AbortSignal | undefined,\n): Promise<TransferPlanResult> {\n let realSource: string;\n let opSource: string;\n let validDest: string;\n try {\n ({ realSource, opSource } = await validateTransferSource(op, pair.source, fs));\n validDest = await fs.pathGuard.validatePathForWrite(pair.destination);\n if (!(op === 'move' && opSource !== realSource) && (await stat(realSource)).isDirectory()) {\n await assertTreeHasNoProtectedEntries(op, realSource, pair.source, validDest, fs, signal);\n }\n } catch (error) {\n return { status: 'fail', failure: pairFailure(pair, error) };\n }\n\n // Comparisons run on the resolved source; only the fs call in phase 2 uses\n // opSource. validatePathForWrite resolves the destination through a symlink\n // too, so both sides of every check must be resolved to match.\n const resolvedSource = resolve(realSource);\n let resolvedDest = resolve(validDest);\n\n // validatePathForWrite realpaths an existing destination to its on-disk\n // spelling, so a case-only rename resolves to the source itself; re-spell\n // it with the requested basename before deciding self vs. rename.\n if (resolvedSource === resolvedDest) {\n validDest = respellCaseOnlyTarget(pair.destination, validDest);\n resolvedDest = resolve(validDest);\n }\n\n const isSelf = resolvedSource === resolvedDest;\n const isCaseOnlyRename = !isSelf && isSamePath(resolvedSource, resolvedDest);\n\n // A copy onto a case-only variant of its own source is the same file, so it\n // is a no-op; the same rename is real work for move and proceeds.\n if (isSelf || (isCaseOnlyRename && op === 'copy')) {\n return { status: 'noop' };\n }\n\n if (!isCaseOnlyRename && isPathInsideDirectory(resolvedSource, resolvedDest)) {\n return {\n status: 'fail',\n failure: pairFailure(\n pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `Cannot ${op} a directory into its own subdirectory`,\n pair.source,\n ),\n ),\n };\n }\n\n const destExistedOriginally = !isCaseOnlyRename && (await destExists(fs, validDest, op));\n const pending = destExistedOriginally && !overwrite;\n\n return {\n status: 'plan',\n plan: {\n pair,\n opSource,\n resolvedSource,\n validDest,\n isCaseOnlyRename,\n destExistedOriginally,\n pending,\n },\n };\n}\n\n/**\n * Phase 2 (mutation): confirm an overwrite if needed, TOCTOU-check the\n * destination, create its parent, then rename (with cross-device fallback) or\n * copy. A declined/missing confirmation throws `CANCELLED`, collected as a\n * per-pair failure by the caller. A destination that appeared between plan and\n * mutation (created during the confirmation gap) also fails closed.\n */\nasync function executeTransfer(\n op: PairOp,\n plan: TransferPlan,\n ctx: Pick<ToolCtx, 'fs' | 'signal' | 'inputResponses' | 'droppedInputResponseKeys' | 'log'>,\n pendingSorted: readonly string[],\n): Promise<TransferExecResult> {\n if (plan.pending) {\n const key = confirmKey(pendingSorted.indexOf(plan.validDest));\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') {\n return { skipped: plan.pair.destination };\n }\n if (choice !== 'overwrite') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: overwrite of \"${plan.pair.destination}\" was ${describeRefusal(ctx, key)}`,\n plan.pair.destination,\n );\n }\n }\n\n // TOCTOU check before any mutation: a destination that did not exist when\n // planned but exists now was created during the confirmation gap.\n if (\n !plan.isCaseOnlyRename &&\n !plan.destExistedOriginally &&\n (await destExists(ctx.fs, plan.validDest, op))\n ) {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: destination \"${plan.pair.destination}\" was created during confirmation.`,\n plan.pair.destination,\n );\n }\n\n await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });\n\n if (op === 'move') {\n await performRenameWithFallback(plan.opSource, plan.validDest, ctx.fs, plan.pair.source);\n ctx.log('info', `move: ${plan.pair.source} -> ${plan.pair.destination}`, 'move');\n } else {\n await ctx.fs.cp(plan.opSource, plan.validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n force: true,\n });\n }\n return { value: { from: plan.opSource, to: plan.validDest } };\n}\n\ntype TransferPlanResult =\n | { status: 'fail'; failure: MoveFailureItem }\n | { status: 'noop' }\n | { status: 'plan'; plan: TransferPlan };\n\n/** A discriminated union, so the fold below narrows without a generic cast. */\ntype TransferExecResult = { readonly skipped: string } | { readonly value: MoveItemResult };\n\nfunction pairFailure(\n pair: { source: string; destination: string },\n error: unknown,\n): MoveFailureItem {\n return {\n source: pair.source,\n destination: pair.destination,\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, pair.source),\n };\n}\n\n/**\n * The source→destination sibling of `runOverPaths`: plan every pair, fail closed\n * on a duplicated destination, round-trip the overwrite confirmations as one\n * set, then execute what survived in parallel. `copy` and `move` differ only in\n * their `plan` and `execute` callbacks.\n */\nasync function runTransfers(\n items: readonly z.infer<typeof MoveItemSchema>[],\n ctx: ToolCtx,\n op: PairOp,\n overwrite: boolean,\n): Promise<\n | { results: MoveItemResult[]; skipped: string[]; failures: MoveFailureItem[] }\n | InputRequiredResult\n> {\n const { results: planned, errors: planErrors } = await processInParallel(\n items,\n (pair) => planTransfer(op, pair, ctx.fs, overwrite, ctx.signal),\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n // plan callbacks are fail-closed (they return { status: 'fail' }, never\n // throw), so planErrors should always be empty. Surface the first rather\n // than silently dropping the item if a future plan callback violates that.\n const firstPlanError = planErrors[0];\n if (firstPlanError) throw firstPlanError.error;\n\n const failures: MoveFailureItem[] = [];\n const candidates: TransferPlan[] = [];\n for (const { value } of planned) {\n if (value.status === 'fail') failures.push(value.failure);\n else if (value.status === 'plan') candidates.push(value.plan);\n // 'noop' (self-copy / self-move) is silently skipped.\n }\n\n // Two sources targeting the same destination in one batch would otherwise\n // collapse to a single shared overwrite confirmation and let the second\n // one silently clobber the first's freshly-written content. Fail closed:\n // only the first plan per destination proceeds; later ones targeting the\n // same destination are reported as a per-item failure.\n const seenDest = new Set<string>();\n const ready: TransferPlan[] = [];\n for (const candidate of candidates) {\n const destKey = normalizeCaseForComparison(candidate.validDest);\n if (seenDest.has(destKey)) {\n failures.push(\n pairFailure(\n candidate.pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `${VERB[op]} cancelled: another entry in this batch already targets destination \"${candidate.pair.destination}\"`,\n candidate.pair.destination,\n ),\n ),\n );\n continue;\n }\n seenDest.add(destKey);\n ready.push(candidate);\n }\n\n // One entry's (resolved) source overlapping another's destination means the\n // two race and a file is lost: a swap [A->B, B->A] or a chain [B->C, A->B].\n // Destination subtrees must also be disjoint: two recursive copies otherwise\n // race over shared descendants even when their destination paths differ.\n // Refuse both sides so the survivors are independent and can run concurrently.\n // isPathInsideDirectory covers equality and containment, case-folded.\n const touches = (x: string, y: string): boolean =>\n isPathInsideDirectory(x, y) || isPathInsideDirectory(y, x);\n const overlapping = new Set<TransferPlan>();\n for (const [i, a] of ready.entries()) {\n for (const b of ready.slice(i + 1)) {\n if (\n touches(a.resolvedSource, b.validDest) ||\n touches(b.resolvedSource, a.validDest) ||\n touches(a.validDest, b.validDest)\n ) {\n overlapping.add(a);\n overlapping.add(b);\n }\n }\n }\n const safe: TransferPlan[] = [];\n for (const plan of ready) {\n if (!overlapping.has(plan)) {\n safe.push(plan);\n continue;\n }\n failures.push(\n pairFailure(\n plan.pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `${VERB[op]} cancelled: \"${plan.pair.source}\" or \"${plan.pair.destination}\" overlaps another entry's source or destination in this batch. Issue dependent moves one at a time.`,\n plan.pair.source,\n ),\n ),\n );\n }\n\n const pendingSorted = safe\n .filter((p) => p.pending)\n .map((p) => p.validDest)\n .sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip(ctx, {\n op,\n pending: pendingSorted,\n buildInputs: (dests) =>\n dests.map((dest, i) => ({\n key: confirmKey(i),\n message:\n op === 'move'\n ? `\"${dest}\" already exists. Overwrite it?`\n : `Destination \"${dest}\" already exists. Overwrite it?`,\n choices: ['overwrite', 'skip'],\n })),\n });\n if (round !== undefined) return round;\n }\n\n const total = safe.length;\n let completed = 0;\n const tick = (): void => {\n completed += 1;\n ctx.onProgress({ current: completed, total });\n };\n const { results: execResults, errors: execErrors } = await processInParallel(\n safe,\n async (readyPlan) => {\n try {\n return await executeTransfer(op, readyPlan, ctx, pendingSorted);\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n for (const { error, index } of execErrors) {\n rethrowIfAborted(error);\n const failed = safe[index];\n if (failed) failures.push(pairFailure(failed.pair, error));\n }\n\n const results: MoveItemResult[] = [];\n const skipped: string[] = [];\n for (const { value } of execResults) {\n if ('skipped' in value) skipped.push(value.skipped);\n else results.push(value.value);\n }\n\n return { results, skipped, failures };\n}\n\nfunction buildSummary(\n verb: 'move' | 'copy',\n results: readonly MoveItemResult[],\n failures: readonly MoveFailureItem[],\n skipped: readonly string[],\n): string {\n const failCount = failures.length;\n // Name each pair. \"move: 2 items\" left the caller to open structuredContent\n // to learn which two, and a partial failure was unreadable without it.\n // Skipped destinations are an outcome the user chose, not a failure — name\n // them, or an all-skipped call reads as \"copy: nothing\".\n const tokens = [\n ...results.map((r) => `${pathLabel(r.from)} → ${pathLabel(r.to)}`),\n ...skipped.map((dest) => `${pathLabel(dest)} SKIPPED`),\n ];\n const parts = [`${verb}: ${joinRoster(tokens) || 'nothing'}`];\n if (failCount > 0) parts.push(`${String(failCount)} failed`);\n return parts.join(' · ');\n}\n\ninterface TransferSource {\n /** Symlink resolved — the identity used for every same-target comparison. */\n realSource: string;\n /** The path the mutation runs on: symlink preserved for move, resolved for copy. */\n opSource: string;\n}\n\n/**\n * A move needs two views of its source. Renaming must operate on the link\n * itself, or moving a symlink would rename the file it points at and leave the\n * link dangling. Every collision check must instead compare the resolved\n * target, or a link moved onto itself (or onto its own target) reads as a real\n * move and renames the link over that target, destroying it. A copy reads\n * through the link, so both views are the resolved one.\n */\nasync function validateTransferSource(\n op: PairOp,\n source: string,\n fs: ToolCtx['fs'],\n): Promise<TransferSource> {\n try {\n const realSource = await fs.pathGuard.validateExistingPath(source);\n const opSource = op === 'move' ? await fs.pathGuard.validatePathForDelete(source) : realSource;\n return { realSource, opSource };\n } catch (error) {\n if (isFsError(error)) throw error;\n throw new FsError(ErrorCode.ACCESS_DENIED, `${VERB[op]} failed for ${source}`, source);\n }\n}\n\n/**\n * A directory transfer may proceed only when every entry keeps the same\n * sensitive-file status before and after relocation. Path-based rules\n * (`secrets/**`, `.aws/credentials`) follow the path, not the file, so refuse\n * any entry that would leave or enter the protected set. Walk the tree once\n * before any mutation and fail the pair closed if anything changes status.\n * Name-based rules need no help — they match after the move too.\n */\nasync function assertTreeHasNoProtectedEntries(\n op: PairOp,\n realSource: string,\n requestedSource: string,\n validDest: string,\n fs: ToolCtx['fs'],\n signal: AbortSignal | undefined,\n): Promise<void> {\n let protectedCount = 0;\n const entries = globEntries({\n cwd: realSource,\n pattern: '**/*',\n includeHidden: true,\n skipIgnored: false,\n onlyFiles: false,\n ...(signal ? { signal } : {}),\n });\n try {\n for await (const entry of entries) {\n signal?.throwIfAborted();\n const entryRelativePath = relative(realSource, entry.path);\n const requestedSourcePath = resolve(requestedSource, entryRelativePath);\n const destinationPath = resolve(validDest, entryRelativePath);\n const protectedBefore =\n fs.pathGuard.isSensitive(entry.path) || fs.pathGuard.isSensitive(requestedSourcePath);\n const protectedAfter = fs.pathGuard.isSensitive(destinationPath);\n if (protectedBefore !== protectedAfter) {\n protectedCount++;\n break;\n }\n }\n } catch (error) {\n rethrowIfAborted(error);\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `${VERB[op]} refused: the directory could not be fully inspected for protected entries`,\n requestedSource,\n error,\n );\n }\n if (protectedCount > 0) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `${VERB[op]} refused: the directory contains protected entries matching the sensitive-file policy; move or copy individual files instead`,\n requestedSource,\n );\n }\n}\n\nexport async function performRenameWithFallback(\n validSource: string,\n validDest: string,\n fsOps: Pick<GuardedFileSystem, 'rename' | 'cp' | 'rm'>,\n originalSource: string,\n): Promise<void> {\n try {\n await fsOps.rename(validSource, validDest);\n } catch (error: unknown) {\n rethrowIfAborted(error);\n\n if (!isNodeError(error) || error.code !== 'EXDEV') {\n throw error;\n }\n\n // EXDEV: cross-device rename. Copy then remove, preserving symlinks and\n // timestamps so link semantics survive the move.\n let copied = false;\n try {\n await fsOps.cp(validSource, validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n });\n copied = true;\n await fsOps.rm(validSource, { recursive: true, force: true });\n } catch (copyOrRemoveError) {\n rethrowIfAborted(copyOrRemoveError);\n if (copied) {\n // cp succeeded but rm failed: the destination already holds a complete\n // copy and the source remains. Surface the rm error as the cause so the\n // caller can recover (clean up the duplicate) instead of a silent generic\n // failure that hides the partial completion.\n const baseProblem = Problem.fromUnknown(\n copyOrRemoveError,\n ErrorCode.UNKNOWN,\n originalSource,\n );\n throw new FsError(\n baseProblem.code,\n `Cross-device move of ${originalSource}: copy succeeded but source removal failed (destination holds a copy): ${baseProblem.message}`,\n originalSource,\n copyOrRemoveError,\n );\n }\n throw copyOrRemoveError;\n }\n }\n}\n\nexport const MOVE = defineTool<typeof MoveInputSchema, MoveOutput>({\n name: 'move',\n title: 'Move or Copy Files',\n description:\n 'Move, rename, or copy files and directories, like mv or cp -r. ' +\n 'Each destination is the full new path, not a folder to move into; missing parent directories are created.',\n input: MoveInputSchema,\n readOnlyHint: false,\n progress: (args) => {\n const label = args.copy ? 'Copy' : 'Move';\n if (args.moves.length === 1) {\n const move = args.moves[0];\n return {\n label,\n subject: `${basename(move?.source ?? '')} → ${basename(move?.destination ?? '')}`,\n };\n }\n return { label, subject: `${String(args.moves.length)} files` };\n },\n accessPaths: (args) => args.moves.flatMap((m) => [m.source, m.destination]),\n run: async (args, ctx) => {\n const op: PairOp = args.copy ? 'copy' : 'move';\n // `overwrite` is copy-only; move has no confirmation bypass.\n const outcome = await runTransfers(args.moves, ctx, op, args.copy && args.overwrite);\n // input_required is a return value, not a completed call: surface it\n // verbatim so the executor short-circuits before building a CallToolResult.\n if (isInputRequiredResult(outcome)) return outcome;\n const { results, skipped, failures } = outcome;\n return {\n structured: {\n moves: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n },\n text: buildSummary(op, results, failures, skipped),\n // Every requested pair failed: no move, no copy, no user-chosen skip - the\n // call did nothing. A skip is work the caller asked for, so it counts.\n isError: failures.length > 0 && results.length + skipped.length === 0,\n };\n },\n});\n"]}
1
+ {"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputRequiredResult } from '@modelcontextprotocol/server';\nimport { isInputRequiredResult } from '@modelcontextprotocol/server';\n\nimport { basename, dirname, relative, resolve } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.ts';\nimport {\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.ts';\nimport { joinRoster, pathLabel } from '../core/fmt.ts';\nimport { destExists } from '../core/fs.ts';\nimport type { GuardedFileSystem } from '../core/fs.ts';\nimport { globEntries } from '../core/glob.ts';\nimport { confirmKey, pendingRoundTrip, readPendingVerdict } from '../core/input-required.ts';\nimport {\n isPathInsideDirectory,\n isSamePath,\n normalizeCaseForComparison,\n respellCaseOnlyTarget,\n} from '../core/path-utils.ts';\nimport { defaultFalseBoolean, RequiredPath } from '../core/schema.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\nimport { defineTool } from './define.ts';\n\nconst MoveItemSchema = z.strictObject({\n source: RequiredPath.describe('Path of the file or directory to move or copy'),\n destination: RequiredPath.describe('Destination path'),\n});\n\ninterface MoveItemResult {\n from: string;\n to: string;\n}\n\nconst MoveInputSchema = z.strictObject({\n moves: z.array(MoveItemSchema).min(1).max(100).describe('Operations to perform (max 100)'),\n copy: defaultFalseBoolean('Copy instead of move; sources are left in place'),\n overwrite: defaultFalseBoolean(\n 'Copy mode only: overwrite existing destinations without confirmation',\n ),\n});\n\ninterface MoveFailureItem {\n source: string;\n destination: string;\n error: Problem;\n}\n\ninterface MoveOutput {\n moves: MoveItemResult[];\n failures?: MoveFailureItem[];\n skipped?: string[];\n}\n\ntype PairOp = 'move' | 'copy';\n\nconst VERB: Readonly<Record<PairOp, string>> = { move: 'Move', copy: 'Copy' };\n\n/** A move or copy pre-checked up to the point a confirmation decision is needed. */\ninterface TransferPlan {\n pair: { source: string; destination: string };\n /**\n * The path the mutation runs on: the link itself for a move, the resolved\n * target for a copy. Every collision check uses the resolved source instead\n * — see {@link validateTransferSource}.\n */\n opSource: string;\n /** Symlink-resolved source, for cross-entry overlap detection (opSource may be the link). */\n resolvedSource: string;\n validDest: string;\n /**\n * A rename that only changes the destination's case. Move must perform it —\n * it is real work — so it is exempt from the destination-exists checks that\n * would otherwise read the source itself as an existing destination. Never\n * true for a copy, which treats a case-only target as a self-copy and skips.\n */\n isCaseOnlyRename: boolean;\n /** Whether the destination existed when planned (drives the TOCTOU guard). */\n destExistedOriginally: boolean;\n /** Destination exists and is not a self/case-only target → needs overwrite confirmation. */\n pending: boolean;\n}\n\n/**\n * Phase 1 (no mutation): validate source and destination, reject self-targets\n * and transfers into a subdirectory of the source, and stat the destination to\n * decide whether this pair needs an overwrite confirmation. No `mkdir` happens\n * here — the old flow created the destination's parent before asking, which\n * mutated the filesystem before a confirmation; that now waits for phase 2 (R14).\n *\n * `overwrite` is copy-only — move has no confirmation bypass — so a move always\n * passes false.\n */\nasync function planTransfer(\n op: PairOp,\n pair: { source: string; destination: string },\n fs: ToolCtx['fs'],\n overwrite: boolean,\n signal: AbortSignal | undefined,\n): Promise<TransferPlanResult> {\n let realSource: string;\n let opSource: string;\n let validDest: string;\n try {\n const source = await validateTransferSource(op, pair.source, fs, signal);\n ({ realSource, opSource } = source);\n validDest = await fs.pathGuard.validatePathForWrite(pair.destination);\n if (!(op === 'move' && opSource !== realSource) && source.isDirectory) {\n await assertTreeHasNoProtectedEntries(op, realSource, pair.source, validDest, fs, signal);\n }\n } catch (error) {\n return { status: 'fail', failure: pairFailure(pair, error) };\n }\n\n // Comparisons run on the resolved source; only the fs call in phase 2 uses\n // opSource. validatePathForWrite resolves the destination through a symlink\n // too, so both sides of every check must be resolved to match.\n const resolvedSource = resolve(realSource);\n let resolvedDest = resolve(validDest);\n\n // validatePathForWrite realpaths an existing destination to its on-disk\n // spelling, so a case-only rename resolves to the source itself; re-spell\n // it with the requested basename before deciding self vs. rename.\n if (resolvedSource === resolvedDest) {\n validDest = respellCaseOnlyTarget(pair.destination, validDest);\n resolvedDest = resolve(validDest);\n }\n\n const isSelf = resolvedSource === resolvedDest;\n const isCaseOnlyRename = !isSelf && isSamePath(resolvedSource, resolvedDest);\n\n // A copy onto a case-only variant of its own source is the same file, so it\n // is a no-op; the same rename is real work for move and proceeds.\n if (isSelf || (isCaseOnlyRename && op === 'copy')) {\n return { status: 'noop' };\n }\n\n if (!isCaseOnlyRename && isPathInsideDirectory(resolvedSource, resolvedDest)) {\n return {\n status: 'fail',\n failure: pairFailure(\n pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `Cannot ${op} a directory into its own subdirectory`,\n pair.source,\n ),\n ),\n };\n }\n\n const destExistedOriginally = !isCaseOnlyRename && (await destExists(fs, validDest, op));\n const pending = destExistedOriginally && !overwrite;\n\n return {\n status: 'plan',\n plan: {\n pair,\n opSource,\n resolvedSource,\n validDest,\n isCaseOnlyRename,\n destExistedOriginally,\n pending,\n },\n };\n}\n\n/**\n * Phase 2 (mutation): confirm an overwrite if needed, TOCTOU-check the\n * destination, create its parent, then rename (with cross-device fallback) or\n * copy. A declined/missing confirmation throws `CANCELLED`, collected as a\n * per-pair failure by the caller. A destination that appeared between plan and\n * mutation (created during the confirmation gap) also fails closed.\n */\nasync function executeTransfer(\n op: PairOp,\n plan: TransferPlan,\n ctx: Pick<ToolCtx, 'fs' | 'signal' | 'inputResponses' | 'droppedInputResponseKeys' | 'log'>,\n pendingSorted: readonly string[],\n): Promise<TransferExecResult> {\n if (plan.pending) {\n const verdict = readPendingVerdict(ctx, pendingSorted, plan.validDest, 'overwrite');\n if (verdict === 'skipped') {\n return { skipped: plan.pair.destination };\n }\n if (verdict !== 'accepted') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: overwrite of \"${plan.pair.destination}\" was ${verdict.refused}`,\n plan.pair.destination,\n );\n }\n }\n\n // TOCTOU check before any mutation: a destination that did not exist when\n // planned but exists now was created during the confirmation gap.\n if (\n !plan.isCaseOnlyRename &&\n !plan.destExistedOriginally &&\n (await destExists(ctx.fs, plan.validDest, op))\n ) {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: destination \"${plan.pair.destination}\" was created during confirmation.`,\n plan.pair.destination,\n );\n }\n\n await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });\n\n if (op === 'move') {\n await performRenameWithFallback(plan.opSource, plan.validDest, ctx.fs, plan.pair.source);\n ctx.log('info', `move: ${plan.pair.source} -> ${plan.pair.destination}`, 'move');\n } else {\n await ctx.fs.cp(plan.opSource, plan.validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n force: true,\n });\n }\n return { value: { from: plan.opSource, to: plan.validDest } };\n}\n\ntype TransferPlanResult =\n | { status: 'fail'; failure: MoveFailureItem }\n | { status: 'noop' }\n | { status: 'plan'; plan: TransferPlan };\n\n/** A discriminated union, so the fold below narrows without a generic cast. */\ntype TransferExecResult = { readonly skipped: string } | { readonly value: MoveItemResult };\n\nfunction pairFailure(\n pair: { source: string; destination: string },\n error: unknown,\n): MoveFailureItem {\n return {\n source: pair.source,\n destination: pair.destination,\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, pair.source),\n };\n}\n\n/**\n * The source→destination sibling of `runOverPaths`: plan every pair, fail closed\n * on a duplicated destination, round-trip the overwrite confirmations as one\n * set, then execute what survived in parallel. `copy` and `move` differ only in\n * their `plan` and `execute` callbacks.\n */\nasync function runTransfers(\n items: readonly z.infer<typeof MoveItemSchema>[],\n ctx: ToolCtx,\n op: PairOp,\n overwrite: boolean,\n): Promise<\n | { results: MoveItemResult[]; skipped: string[]; failures: MoveFailureItem[] }\n | InputRequiredResult\n> {\n const { results: planned, errors: planErrors } = await processInParallel(\n items,\n (pair) => planTransfer(op, pair, ctx.fs, overwrite, ctx.signal),\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n // plan callbacks are fail-closed (they return { status: 'fail' }, never\n // throw), so planErrors should always be empty. Surface the first rather\n // than silently dropping the item if a future plan callback violates that.\n const firstPlanError = planErrors[0];\n if (firstPlanError) throw firstPlanError.error;\n\n const failures: MoveFailureItem[] = [];\n const candidates: TransferPlan[] = [];\n for (const { value } of planned) {\n if (value.status === 'fail') failures.push(value.failure);\n else if (value.status === 'plan') candidates.push(value.plan);\n // 'noop' (self-copy / self-move) is silently skipped.\n }\n\n // Two sources targeting the same destination in one batch would otherwise\n // collapse to a single shared overwrite confirmation and let the second\n // one silently clobber the first's freshly-written content. Fail closed:\n // only the first plan per destination proceeds; later ones targeting the\n // same destination are reported as a per-item failure.\n const seenDest = new Set<string>();\n const ready: TransferPlan[] = [];\n for (const candidate of candidates) {\n const destKey = normalizeCaseForComparison(candidate.validDest);\n if (seenDest.has(destKey)) {\n failures.push(\n pairFailure(\n candidate.pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `${VERB[op]} cancelled: another entry in this batch already targets destination \"${candidate.pair.destination}\"`,\n candidate.pair.destination,\n ),\n ),\n );\n continue;\n }\n seenDest.add(destKey);\n ready.push(candidate);\n }\n\n // One entry's (resolved) source overlapping another's destination means the\n // two race and a file is lost: a swap [A->B, B->A] or a chain [B->C, A->B].\n // Destination subtrees must also be disjoint: two recursive copies otherwise\n // race over shared descendants even when their destination paths differ.\n // Refuse both sides so the survivors are independent and can run concurrently.\n // isPathInsideDirectory covers equality and containment, case-folded.\n const touches = (x: string, y: string): boolean =>\n isPathInsideDirectory(x, y) || isPathInsideDirectory(y, x);\n const overlapping = new Set<TransferPlan>();\n for (const [i, a] of ready.entries()) {\n for (const b of ready.slice(i + 1)) {\n if (\n touches(a.resolvedSource, b.validDest) ||\n touches(b.resolvedSource, a.validDest) ||\n touches(a.validDest, b.validDest)\n ) {\n overlapping.add(a);\n overlapping.add(b);\n }\n }\n }\n const safe: TransferPlan[] = [];\n for (const plan of ready) {\n if (!overlapping.has(plan)) {\n safe.push(plan);\n continue;\n }\n failures.push(\n pairFailure(\n plan.pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `${VERB[op]} cancelled: \"${plan.pair.source}\" or \"${plan.pair.destination}\" overlaps another entry's source or destination in this batch. Issue dependent moves one at a time.`,\n plan.pair.source,\n ),\n ),\n );\n }\n\n const pendingSorted = safe\n .filter((p) => p.pending)\n .map((p) => p.validDest)\n .sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip(ctx, {\n op,\n pending: pendingSorted,\n buildInputs: (dests) =>\n dests.map((dest, i) => ({\n key: confirmKey(i),\n message:\n op === 'move'\n ? `\"${dest}\" already exists. Overwrite it?`\n : `Destination \"${dest}\" already exists. Overwrite it?`,\n choices: ['overwrite', 'skip'],\n })),\n });\n if (round !== undefined) return round;\n }\n\n const total = safe.length;\n let completed = 0;\n const tick = (): void => {\n completed += 1;\n ctx.onProgress({ current: completed, total });\n };\n const { results: execResults, errors: execErrors } = await processInParallel(\n safe,\n async (readyPlan) => {\n try {\n return await executeTransfer(op, readyPlan, ctx, pendingSorted);\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n for (const { error, index } of execErrors) {\n rethrowIfAborted(error);\n const failed = safe[index];\n if (failed) failures.push(pairFailure(failed.pair, error));\n }\n\n const results: MoveItemResult[] = [];\n const skipped: string[] = [];\n for (const { value } of execResults) {\n if ('skipped' in value) skipped.push(value.skipped);\n else results.push(value.value);\n }\n\n return { results, skipped, failures };\n}\n\nfunction buildSummary(\n verb: 'move' | 'copy',\n results: readonly MoveItemResult[],\n failures: readonly MoveFailureItem[],\n skipped: readonly string[],\n): string {\n const failCount = failures.length;\n // Name each pair. \"move: 2 items\" left the caller to open structuredContent\n // to learn which two, and a partial failure was unreadable without it.\n // Skipped destinations are an outcome the user chose, not a failure — name\n // them, or an all-skipped call reads as \"copy: nothing\".\n const tokens = [\n ...results.map((r) => `${pathLabel(r.from)} → ${pathLabel(r.to)}`),\n ...skipped.map((dest) => `${pathLabel(dest)} SKIPPED`),\n ];\n const parts = [`${verb}: ${joinRoster(tokens) || 'nothing'}`];\n if (failCount > 0) parts.push(`${String(failCount)} failed`);\n return parts.join(' · ');\n}\n\ninterface TransferSource {\n /** Symlink resolved — the identity used for every same-target comparison. */\n realSource: string;\n /** The path the mutation runs on: symlink preserved for move, resolved for copy. */\n opSource: string;\n /** Whether the resolved source is a directory (from the same guarded stat). */\n isDirectory: boolean;\n}\n\n/**\n * A move needs two views of its source. Renaming must operate on the link\n * itself, or moving a symlink would rename the file it points at and leave the\n * link dangling. Every collision check must instead compare the resolved\n * target, or a link moved onto itself (or onto its own target) reads as a real\n * move and renames the link over that target, destroying it. A copy reads\n * through the link, so both views are the resolved one.\n */\nasync function validateTransferSource(\n op: PairOp,\n source: string,\n fs: ToolCtx['fs'],\n signal: AbortSignal | undefined,\n): Promise<TransferSource> {\n try {\n const { stats, validPath: realSource } = await fs.stat(source, { signal });\n const opSource = op === 'move' ? await fs.pathGuard.validatePathForDelete(source) : realSource;\n return { realSource, opSource, isDirectory: stats.isDirectory() };\n } catch (error) {\n if (isFsError(error) || signal?.aborted) throw error;\n throw new FsError(ErrorCode.ACCESS_DENIED, `${VERB[op]} failed for ${source}`, source);\n }\n}\n\n/**\n * A directory transfer may proceed only when every entry keeps the same\n * sensitive-file status before and after relocation. Path-based rules\n * (`secrets/**`, `.aws/credentials`) follow the path, not the file, so refuse\n * any entry that would leave or enter the protected set. Walk the tree once\n * before any mutation and fail the pair closed if anything changes status.\n * Name-based rules need no help — they match after the move too.\n */\nasync function assertTreeHasNoProtectedEntries(\n op: PairOp,\n realSource: string,\n requestedSource: string,\n validDest: string,\n fs: ToolCtx['fs'],\n signal: AbortSignal | undefined,\n): Promise<void> {\n let protectedCount = 0;\n const entries = globEntries({\n cwd: realSource,\n pattern: '**/*',\n includeHidden: true,\n skipIgnored: false,\n onlyFiles: false,\n ...(signal ? { signal } : {}),\n });\n try {\n for await (const entry of entries) {\n signal?.throwIfAborted();\n const entryRelativePath = relative(realSource, entry.path);\n const requestedSourcePath = resolve(requestedSource, entryRelativePath);\n const destinationPath = resolve(validDest, entryRelativePath);\n const protectedBefore =\n fs.pathGuard.isSensitive(entry.path) || fs.pathGuard.isSensitive(requestedSourcePath);\n const protectedAfter = fs.pathGuard.isSensitive(destinationPath);\n if (protectedBefore !== protectedAfter) {\n protectedCount++;\n break;\n }\n }\n } catch (error) {\n rethrowIfAborted(error);\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `${VERB[op]} refused: the directory could not be fully inspected for protected entries`,\n requestedSource,\n error,\n );\n }\n if (protectedCount > 0) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `${VERB[op]} refused: the directory contains protected entries matching the sensitive-file policy; move or copy individual files instead`,\n requestedSource,\n );\n }\n}\n\nexport async function performRenameWithFallback(\n validSource: string,\n validDest: string,\n fsOps: Pick<GuardedFileSystem, 'rename' | 'cp' | 'rm'>,\n originalSource: string,\n): Promise<void> {\n try {\n await fsOps.rename(validSource, validDest);\n } catch (error: unknown) {\n rethrowIfAborted(error);\n\n if (!isNodeError(error) || error.code !== 'EXDEV') {\n throw error;\n }\n\n // EXDEV: cross-device rename. Copy then remove, preserving symlinks and\n // timestamps so link semantics survive the move.\n let copied = false;\n try {\n await fsOps.cp(validSource, validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n });\n copied = true;\n await fsOps.rm(validSource, { recursive: true, force: true });\n } catch (copyOrRemoveError) {\n rethrowIfAborted(copyOrRemoveError);\n if (copied) {\n // cp succeeded but rm failed: the destination already holds a complete\n // copy and the source remains. Surface the rm error as the cause so the\n // caller can recover (clean up the duplicate) instead of a silent generic\n // failure that hides the partial completion.\n const baseProblem = Problem.fromUnknown(\n copyOrRemoveError,\n ErrorCode.UNKNOWN,\n originalSource,\n );\n throw new FsError(\n baseProblem.code,\n `Cross-device move of ${originalSource}: copy succeeded but source removal failed (destination holds a copy): ${baseProblem.message}`,\n originalSource,\n copyOrRemoveError,\n );\n }\n throw copyOrRemoveError;\n }\n }\n}\n\nexport const MOVE = defineTool<typeof MoveInputSchema, MoveOutput>({\n name: 'move',\n title: 'Move or Copy Files',\n description:\n 'Move, rename, or copy files and directories, like mv or cp -r. ' +\n 'Each destination is the full new path, not a folder to move into; missing parent directories are created.',\n input: MoveInputSchema,\n readOnlyHint: false,\n progress: (args) => {\n const label = args.copy ? 'Copy' : 'Move';\n if (args.moves.length === 1) {\n const move = args.moves[0];\n return {\n label,\n subject: `${basename(move?.source ?? '')} → ${basename(move?.destination ?? '')}`,\n };\n }\n return { label, subject: `${String(args.moves.length)} files` };\n },\n accessPaths: (args) => args.moves.flatMap((m) => [m.source, m.destination]),\n run: async (args, ctx) => {\n const op: PairOp = args.copy ? 'copy' : 'move';\n // `overwrite` is copy-only; move has no confirmation bypass.\n const outcome = await runTransfers(args.moves, ctx, op, args.copy && args.overwrite);\n // input_required is a return value, not a completed call: surface it\n // verbatim so the executor short-circuits before building a CallToolResult.\n if (isInputRequiredResult(outcome)) return outcome;\n const { results, skipped, failures } = outcome;\n return {\n structured: {\n moves: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n },\n text: buildSummary(op, results, failures, skipped),\n // Every requested pair failed: no move, no copy, no user-chosen skip - the\n // call did nothing. A skip is work the caller asked for, so it counts.\n isError: failures.length > 0 && results.length + skipped.length === 0,\n };\n },\n});\n"]}
@@ -59,7 +59,7 @@ import { ErrorCode, FsError, Problem } from "../core/errors.js";
59
59
  import { buildWrittenFileMeta } from "../core/file-uri.js";
60
60
  import { stoppedEarlyLine, truncateProgressPattern, zeroMatchHint } from "../core/fmt.js";
61
61
  import { globEntries } from "../core/glob.js";
62
- import { toPosixRelative } from "../core/path.js";
62
+ import { toPosixRelative } from "../core/path-utils.js";
63
63
  import { createTooLargeError, readFileBufferWithLimit } from "../core/read.js";
64
64
  import { defaultFalseBoolean, IncludeHidden, IncludeIgnored, MaxDepth, nonBlankString, OptionalPath, SafeGlobPattern, } from "../core/schema.js";
65
65
  import { compileRegex, createYielder, execMatches, freeRegex, RE2_MAX_INPUT_BYTES, } from "../core/search.js";