@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
89 lines • 5.08 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { transferLinksOf } from "../../generic/crossmount/utils.js";
import { treeWalk } from "../../utils/tree.js";
import { walkFind } from "../../../../core/generic/find.js";
import { stripSlash } from "../../../../utils/slash.js";
import { cpGeneric, facts, parseFlags } from "../../generic/cp.js";
import { serves, typedLink, linksOf, mountsOf, dispatchOf, mountRoot } from "../../utils/links.js";
import { enotsup } from "../../../../errors/fs.js";
import { commandFiles } from "../files.js";
import { FlagView } from "../../../spec/flag_view.js";
import { pathsScoped } from "../../../../view/namespace_view.js";
import { specOf } from "../../../spec/builtins.js";
export const BUILDER = {
name: 'cp',
facts: facts,
write: true,
fn: async (_accessor, paths, _texts, ctx) => {
const files = commandFiles(ctx, paths);
// A native copy or find answers for a whole tree in one call, so the
// dispatcher declines one whose tree the caller's view restricts; cp
// takes the entry-by-entry walk up front there, which is where GNU's
// per-entry refusals are worded.
const scoped = pathsScoped(paths, mountRoot(ctx));
const copies = !scoped && files.answers('copy');
const writes = files.answers('write');
// A function cp may not use here, refused once the dispatcher has judged
// the path as the write it would be.
const refused = (name) => async (path) => {
await ctx.vfs?.dispatch('write', path, [], { admit_only: true });
throw enotsup('backend', name, path);
};
// Without a file transfer capability, creating directories would
// leave an uncopyable destination tree. Keep the refusal guarded.
const mkdir = !copies && !writes ? refused('mkdir') : files.answers('mkdir') ? files.mkdir : undefined;
const resolved = await files.resolveGlob(paths);
// The tree to copy, walked from the mount's readTree where it answers
// (mirrors Python's _make_find), so stat classifies entries from the
// listing instead of re-fetching, matching find.
const [readdir, stat] = treeWalk(files.readTree, (p) => files.readdir(p), files.stat, files.answers('read_tree'), files.signal);
const findFn = async (src, options) => {
const refused = [];
const found = await walkFind(src, { readdir, stat, ...(options.unreadable === undefined ? {} : { unreadable: refused }) }, { type: options.type });
options.unreadable?.push(...refused.map((path) => `/${stripSlash(path)}`));
return found;
};
const parsed = parseFlags(new FlagView(ctx.flags, specOf('cp')));
// A native copy moves a tree in one backend call and readTree lists
// it, neither of which passes an entry through the guard the way a
// read does; while a path rule scopes cp, or a hide could cover an
// entry under an operand (a one-call listing names hidden entries and
// the per-file read then prints them in its refusal),
// the primitive walk copies entry by entry (the cross-mount
// relay's own path), which is also where GNU's per-entry refusals
// are worded.
const strategy = !copies && writes
? {
read: files.read,
write: files.write,
mkdir: (p) => files.mkdir(p),
readdir: files.readdir,
}
: {
// One file's copy goes to the dispatcher whatever the view
// restricts: it judges both ends of the copy itself.
copy: files.copy,
find: findFn,
...(!scoped && files.answers('dir_copy') ? { dirCopy: files.dirCopy } : {}),
...(mkdir === undefined ? {} : { mkdir: (p) => mkdir(p) }),
};
const links = linksOf(ctx) ?? null;
const cwd = ctx.cwd.virtual;
return cpGeneric(resolved, files.stat, strategy, parsed, undefined, files.readdir, links === null ? undefined : (p) => typedLink(links, p, cwd), links === null || ctx.vfs === undefined
? undefined
: transferLinksOf(links, ctx.vfs.dispatch, cwd, (virtual) => serves(mountsOf(ctx), mountRoot(ctx), virtual)), ctx.stdin, dispatchOf(ctx));
},
};
//# sourceMappingURL=cp.js.map