@relayfile/local-mount 0.5.2 → 0.6.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/README.md +37 -3
- package/dist/auto-sync.d.ts +7 -0
- package/dist/auto-sync.js +15 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/launch.d.ts +6 -0
- package/dist/launch.js +3 -2
- package/dist/mount.d.ts +38 -0
- package/dist/{symlink-mount.js → mount.js} +21 -8
- package/package.json +1 -1
- package/dist/symlink-mount.d.ts +0 -25
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@ npm install @relayfile/local-mount
|
|
|
12
12
|
|
|
13
13
|
## What it exports
|
|
14
14
|
|
|
15
|
-
### `
|
|
15
|
+
### `createMount(projectDir, mountDir, options)`
|
|
16
16
|
|
|
17
17
|
Builds a mounted copy of `projectDir` at `mountDir` and returns a handle:
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
interface
|
|
20
|
+
interface MountHandle {
|
|
21
21
|
mountDir: string;
|
|
22
22
|
syncBack(opts?: { signal?: AbortSignal }): Promise<number>;
|
|
23
23
|
startAutoSync(opts?: AutoSyncOptions): AutoSyncHandle;
|
|
@@ -29,7 +29,7 @@ Behavior:
|
|
|
29
29
|
- Copies regular files into the mount
|
|
30
30
|
- Applies ignore rules from `ignoredPatterns`
|
|
31
31
|
- Marks read-only matches as mode `0o444`
|
|
32
|
-
- Excludes `.git` and `node_modules` by default
|
|
32
|
+
- Excludes `.git` and `node_modules` by default. Pass `includeGit: true` to opt the project's `.git` directory back in (see [Including `.git`](#including-git))
|
|
33
33
|
- Writes `_MOUNT_README.md` and `.relayfile-local-mount` into the mount
|
|
34
34
|
- Skips syncing `_MOUNT_README.md`, `.relayfile-local-mount`, ignored files, read-only files, and symlinks back to the source project
|
|
35
35
|
|
|
@@ -110,6 +110,28 @@ Conflict and delete rules:
|
|
|
110
110
|
- readonly paths never flow mount→project; project-side edits still flow into the mount (the mount copy is re-chmodded `0o444`)
|
|
111
111
|
- `_MOUNT_README.md`, `.relayfile-local-mount`, ignored paths, and excluded directories never cross
|
|
112
112
|
|
|
113
|
+
## Including `.git`
|
|
114
|
+
|
|
115
|
+
By default, the project's `.git` directory is excluded from the mount, which means git commands inside the mount fail with `fatal: not a git repository`. Pass `includeGit: true` (on `createMount` or `launchOnMount`) to copy `.git` into the mount with **one-way project→mount sync**:
|
|
116
|
+
|
|
117
|
+
- `.git` is copied on mount creation, so `git status`, `git log`, `git diff`, `git commit`, etc. all work inside the mount.
|
|
118
|
+
- Project-side changes under `.git/**` flow into the mount (e.g. if a teammate's tooling moves `HEAD` while the agent is running).
|
|
119
|
+
- Mount-side changes under `.git/**` are **not** synced back to the project. Branches, commits, or refs the agent creates in the mount stay sandboxed and are discarded on cleanup.
|
|
120
|
+
|
|
121
|
+
If the agent needs its commits to survive, push to a remote from inside the mount. Source files outside `.git` continue to follow the normal bidirectional sync rules.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
launchOnMount({
|
|
125
|
+
cli: 'claude',
|
|
126
|
+
args: ['--print', 'Inspect the diff and propose a fix.'],
|
|
127
|
+
projectDir,
|
|
128
|
+
mountDir,
|
|
129
|
+
includeGit: true,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Note that `.git` can be sizable (hundreds of MB on long-lived repos); the initial mount creation copies the whole tree.
|
|
134
|
+
|
|
113
135
|
## Dotfile semantics
|
|
114
136
|
|
|
115
137
|
`@relayfile/local-mount` uses glob-style patterns, powered by [`ignore`](https://www.npmjs.com/package/ignore).
|
|
@@ -213,6 +235,18 @@ The implementation is intentionally conservative about `mountDir`:
|
|
|
213
235
|
|
|
214
236
|
These checks help prevent accidental deletion of unrelated directories during mount recreation and cleanup.
|
|
215
237
|
|
|
238
|
+
## Why copy instead of symlink?
|
|
239
|
+
|
|
240
|
+
The mount is built by copying files rather than symlinking them. Symlinks would break several of the package's guarantees:
|
|
241
|
+
|
|
242
|
+
1. **`.agentreadonly` can't be enforced.** Read-only is implemented by `chmod 0o444` on the mount copy. `chmod` follows symlinks, so applying it to a symlink would mark the *source* file read-only, flipping the project's permissions instead of restricting the agent's view.
|
|
243
|
+
2. **The auto-sync conflict model assumes two distinct files.** Rules like "both sides changed → mount wins", "one side deleted → propagate", and "readonly paths never flow mount→project but project-side edits still flow into the mount" only make sense if mount and source are separate bytes. Through a symlink they're the same inode — there's no mount-side copy to re-chmod `0o444` after a project-side edit.
|
|
244
|
+
3. **Editor save-via-rename breaks symlinks anyway.** Most editors save by writing a temp file and renaming it over the target, which replaces the symlink with a regular file and severs the link. A "live view" via symlinks isn't reliable in practice.
|
|
245
|
+
4. **Containment.** A copy gives you a checkpoint: if the agent destroys files or writes garbage, the source is untouched until `syncBack()` filters and copies back. With symlinks, every keystroke is live on the project.
|
|
246
|
+
5. **`.agentignore` hiding works fine with symlinks** (just don't link), but the readonly and conflict semantics still need copies — so a hybrid would be more complex than just copying everything.
|
|
247
|
+
|
|
248
|
+
Source-side symlinks that resolve to regular files inside the project *are* followed when building the mount; the resolved bytes are copied. Symlinks the agent creates inside the mount are skipped on sync-back.
|
|
249
|
+
|
|
216
250
|
## Notes
|
|
217
251
|
|
|
218
252
|
- Requires Node.js 18+
|
package/dist/auto-sync.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export interface AutoSyncContext {
|
|
|
10
10
|
*/
|
|
11
11
|
isIgnored: (relPosix: string, isDirectory?: boolean) => boolean;
|
|
12
12
|
isReadonly: (relPosix: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* One-way project→mount paths. Project-side changes flow into the mount,
|
|
15
|
+
* but mount-side changes never flow back. Unlike readonly, the mount copy
|
|
16
|
+
* is left writable so tools (e.g. git) can mutate it locally; those
|
|
17
|
+
* mutations are simply discarded on cleanup.
|
|
18
|
+
*/
|
|
19
|
+
isNoSyncBack: (relPosix: string) => boolean;
|
|
13
20
|
isReservedFile: (relPosix: string) => boolean;
|
|
14
21
|
}
|
|
15
22
|
export interface AutoSyncOptions {
|
package/dist/auto-sync.js
CHANGED
|
@@ -262,11 +262,16 @@ function reconcile(state, ctx, onError, signal) {
|
|
|
262
262
|
* Resolution rules ("mount wins"):
|
|
263
263
|
* - If both sides changed since last sync → mount→project.
|
|
264
264
|
* - Only mount changed → mount→project (unless mount-side change is disallowed
|
|
265
|
-
* for readonly files; then drop the mount change).
|
|
265
|
+
* for readonly / noSyncBack files; then drop the mount change).
|
|
266
266
|
* - Only project changed → project→mount.
|
|
267
267
|
* - One side missing:
|
|
268
268
|
* • Other side changed since last sync → recreate the missing side.
|
|
269
269
|
* • Otherwise → propagate the delete.
|
|
270
|
+
*
|
|
271
|
+
* `readonly` and `noSyncBack` both forbid mount→project. The split exists so
|
|
272
|
+
* the chmod 0o444 only fires for true readonly entries (e.g. `.agentreadonly`
|
|
273
|
+
* matches), while noSyncBack entries (e.g. `.git/**` when `includeGit: true`)
|
|
274
|
+
* stay writable in the mount so tools can mutate them locally.
|
|
270
275
|
*/
|
|
271
276
|
function syncOneFile(relPosix, state, ctx) {
|
|
272
277
|
const mountAbs = path.join(ctx.realMountDir, relPosix);
|
|
@@ -275,6 +280,7 @@ function syncOneFile(relPosix, state, ctx) {
|
|
|
275
280
|
const projectStat = safeFileStat(projectAbs);
|
|
276
281
|
const prev = state.get(relPosix);
|
|
277
282
|
const readonly = ctx.isReadonly(relPosix);
|
|
283
|
+
const noSyncBack = readonly || ctx.isNoSyncBack(relPosix);
|
|
278
284
|
if (!mountStat && !projectStat) {
|
|
279
285
|
state.delete(relPosix);
|
|
280
286
|
return false;
|
|
@@ -290,15 +296,15 @@ function syncOneFile(relPosix, state, ctx) {
|
|
|
290
296
|
return false;
|
|
291
297
|
}
|
|
292
298
|
// Differ with no history: arbitrary tiebreak → mount wins.
|
|
293
|
-
if (
|
|
294
|
-
//
|
|
299
|
+
if (noSyncBack) {
|
|
300
|
+
// Mount-side writes never flow back; fall back to project→mount.
|
|
295
301
|
return doProjectToMount(relPosix, state, ctx, projectAbs, mountAbs, readonly);
|
|
296
302
|
}
|
|
297
303
|
return doMountToProject(relPosix, state, ctx, mountAbs, projectAbs);
|
|
298
304
|
}
|
|
299
305
|
if (mountStat && !projectStat) {
|
|
300
|
-
if (
|
|
301
|
-
// New file in mount with a
|
|
306
|
+
if (noSyncBack) {
|
|
307
|
+
// New file in mount with a no-sync-back pattern → cannot sync back.
|
|
302
308
|
return false;
|
|
303
309
|
}
|
|
304
310
|
return doMountToProject(relPosix, state, ctx, mountAbs, projectAbs);
|
|
@@ -319,7 +325,7 @@ function syncOneFile(relPosix, state, ctx) {
|
|
|
319
325
|
if (mountStat && projectStat) {
|
|
320
326
|
if (!mountChanged && !projectChanged)
|
|
321
327
|
return false;
|
|
322
|
-
if (mountChanged && !
|
|
328
|
+
if (mountChanged && !noSyncBack) {
|
|
323
329
|
return doMountToProject(relPosix, state, ctx, mountAbs, projectAbs);
|
|
324
330
|
}
|
|
325
331
|
if (projectChanged) {
|
|
@@ -328,7 +334,7 @@ function syncOneFile(relPosix, state, ctx) {
|
|
|
328
334
|
return false;
|
|
329
335
|
}
|
|
330
336
|
if (mountStat && !projectStat) {
|
|
331
|
-
if (mountChanged && !
|
|
337
|
+
if (mountChanged && !noSyncBack) {
|
|
332
338
|
return doMountToProject(relPosix, state, ctx, mountAbs, projectAbs);
|
|
333
339
|
}
|
|
334
340
|
// Project deleted externally and mount hasn't been touched since → mirror.
|
|
@@ -339,8 +345,8 @@ function syncOneFile(relPosix, state, ctx) {
|
|
|
339
345
|
return doProjectToMount(relPosix, state, ctx, projectAbs, mountAbs, readonly);
|
|
340
346
|
}
|
|
341
347
|
// Mount deleted and project hasn't been touched since → mirror to project.
|
|
342
|
-
if (
|
|
343
|
-
//
|
|
348
|
+
if (noSyncBack) {
|
|
349
|
+
// No-sync-back deletes in mount don't propagate; recreate from project.
|
|
344
350
|
return doProjectToMount(relPosix, state, ctx, projectAbs, mountAbs, readonly);
|
|
345
351
|
}
|
|
346
352
|
return doDeleteProject(relPosix, state, projectAbs);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createMount, type MountOptions, type MountHandle, } from './mount.js';
|
|
2
2
|
export { type AutoSyncOptions, type AutoSyncHandle, } from './auto-sync.js';
|
|
3
3
|
export { readAgentDotfiles, type ReadAgentDotfilesOptions, type AgentDotfilePatterns, } from './dotfiles.js';
|
|
4
4
|
export { launchOnMount, type LaunchOnMountOptions, type LaunchOnMountResult, } from './launch.js';
|
package/dist/index.js
CHANGED
package/dist/launch.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface LaunchOnMountOptions {
|
|
|
14
14
|
readonlyPatterns?: string[];
|
|
15
15
|
/** Extra directory names to exclude from the mount on top of defaults. */
|
|
16
16
|
excludeDirs?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Include the project's `.git` directory inside the mount with one-way
|
|
19
|
+
* project→mount sync. Defaults to false. See {@link MountOptions.includeGit}
|
|
20
|
+
* for details.
|
|
21
|
+
*/
|
|
22
|
+
includeGit?: boolean;
|
|
17
23
|
/** Extra env vars merged on top of `process.env`. */
|
|
18
24
|
env?: NodeJS.ProcessEnv;
|
|
19
25
|
/** Optional agent name, used in the _MOUNT_README.md "Agent:" line. */
|
package/dist/launch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { createMount } from './mount.js';
|
|
3
3
|
/**
|
|
4
4
|
* Create a mount of `projectDir` at `mountDir`, spawn `cli` with `args` using
|
|
5
5
|
* the mount as its cwd, forward SIGINT/SIGTERM to the child, sync writable
|
|
@@ -7,11 +7,12 @@ import { createSymlinkMount } from './symlink-mount.js';
|
|
|
7
7
|
* exit code.
|
|
8
8
|
*/
|
|
9
9
|
export async function launchOnMount(opts) {
|
|
10
|
-
const handle =
|
|
10
|
+
const handle = createMount(opts.projectDir, opts.mountDir, {
|
|
11
11
|
ignoredPatterns: opts.ignoredPatterns ?? [],
|
|
12
12
|
readonlyPatterns: opts.readonlyPatterns ?? [],
|
|
13
13
|
excludeDirs: opts.excludeDirs ?? [],
|
|
14
14
|
agentName: opts.agentName,
|
|
15
|
+
includeGit: opts.includeGit,
|
|
15
16
|
});
|
|
16
17
|
let syncedCount = 0;
|
|
17
18
|
let finalized = false;
|
package/dist/mount.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type AutoSyncHandle, type AutoSyncOptions } from './auto-sync.js';
|
|
2
|
+
export interface MountOptions {
|
|
3
|
+
ignoredPatterns: string[];
|
|
4
|
+
readonlyPatterns: string[];
|
|
5
|
+
excludeDirs: string[];
|
|
6
|
+
/**
|
|
7
|
+
* Optional agent name used in the _MOUNT_README.md "Agent:" line.
|
|
8
|
+
* If omitted, the doc uses a generic "agent" value.
|
|
9
|
+
*/
|
|
10
|
+
agentName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Include the project's `.git` directory inside the mount with one-way
|
|
13
|
+
* project→mount sync. Default: false (`.git` is excluded entirely, matching
|
|
14
|
+
* historical behavior).
|
|
15
|
+
*
|
|
16
|
+
* When true:
|
|
17
|
+
* - `.git` is copied into the mount on creation, so git commands work inside.
|
|
18
|
+
* - Project-side changes under `.git/**` flow into the mount.
|
|
19
|
+
* - Mount-side changes under `.git/**` do NOT flow back to the project, so
|
|
20
|
+
* commits/branches the agent creates inside the mount stay sandboxed and
|
|
21
|
+
* are discarded with the mount on cleanup. Push to a remote to keep them.
|
|
22
|
+
*/
|
|
23
|
+
includeGit?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface MountHandle {
|
|
26
|
+
mountDir: string;
|
|
27
|
+
syncBack(opts?: {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Start bidirectional auto-sync: watches both the mount and project trees
|
|
32
|
+
* via @parcel/watcher and runs a full reconcile every `scanIntervalMs`
|
|
33
|
+
* as a safety net. Returns a handle you must `stop()` before teardown.
|
|
34
|
+
*/
|
|
35
|
+
startAutoSync(opts?: AutoSyncOptions): AutoSyncHandle;
|
|
36
|
+
cleanup(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare function createMount(projectDir: string, mountDir: string, options: MountOptions): MountHandle;
|
|
@@ -6,16 +6,26 @@ const DEFAULT_EXCLUDED_DIRS = ['.git', 'node_modules'];
|
|
|
6
6
|
const MOUNT_README_FILENAME = '_MOUNT_README.md';
|
|
7
7
|
const MOUNT_MARKER_FILENAME = '.relayfile-local-mount';
|
|
8
8
|
const MOUNT_MARKER_CONTENT = 'This directory is managed by @relayfile/local-mount. Do not place unrelated files here; the directory will be deleted when the mount is torn down.\n';
|
|
9
|
-
export function
|
|
9
|
+
export function createMount(projectDir, mountDir, options) {
|
|
10
10
|
const resolvedProjectDir = realpathSync(projectDir);
|
|
11
11
|
const resolvedMountDir = path.resolve(mountDir);
|
|
12
12
|
const readonlyPatterns = [...options.readonlyPatterns];
|
|
13
13
|
const ignoredPatterns = [...options.ignoredPatterns];
|
|
14
|
+
const includeGit = options.includeGit === true;
|
|
14
15
|
const readonlyMatcher = createPathMatcher(readonlyPatterns);
|
|
15
16
|
const ignoredMatcher = createPathMatcher(ignoredPatterns);
|
|
16
|
-
|
|
17
|
+
// `.git` is in DEFAULT_EXCLUDED_DIRS so the mount stays small and git
|
|
18
|
+
// operations don't accidentally cross-mutate the host repo. When the caller
|
|
19
|
+
// opts in via `includeGit`, drop it from the defaults and instead route it
|
|
20
|
+
// through the noSyncBack matcher below so it stays one-way.
|
|
21
|
+
const defaultExcludes = includeGit
|
|
22
|
+
? DEFAULT_EXCLUDED_DIRS.filter((d) => d !== '.git')
|
|
23
|
+
: DEFAULT_EXCLUDED_DIRS;
|
|
24
|
+
const excludeSet = new Set([...defaultExcludes, ...options.excludeDirs]
|
|
17
25
|
.map((entry) => normalizeRelativePosix(entry).replace(/^\/+|\/+$/g, ''))
|
|
18
26
|
.filter(Boolean));
|
|
27
|
+
const noSyncBackPatterns = includeGit ? ['.git', '.git/**'] : [];
|
|
28
|
+
const noSyncBackMatcher = createPathMatcher(noSyncBackPatterns);
|
|
19
29
|
// Guard against mountDir === projectDir. We compare both the realpath'd
|
|
20
30
|
// project dir and the plain resolved project dir so callers that pass the
|
|
21
31
|
// same argument for both are caught even when the path is a symlink (e.g.
|
|
@@ -42,6 +52,7 @@ export function createSymlinkMount(projectDir, mountDir, options) {
|
|
|
42
52
|
isExcluded: (relPosix) => isExcludedPath(relPosix, excludeSet),
|
|
43
53
|
isIgnored: (relPosix, isDir) => isPathMatched(relPosix, ignoredMatcher, isDir),
|
|
44
54
|
isReadonly: (relPosix) => isPathMatched(relPosix, readonlyMatcher),
|
|
55
|
+
isNoSyncBack: (relPosix) => isPathMatched(relPosix, noSyncBackMatcher),
|
|
45
56
|
isReservedFile: (relPosix) => relPosix === MOUNT_README_FILENAME || relPosix === MOUNT_MARKER_FILENAME,
|
|
46
57
|
};
|
|
47
58
|
return {
|
|
@@ -56,7 +67,7 @@ export function createSymlinkMount(projectDir, mountDir, options) {
|
|
|
56
67
|
if (signal?.aborted) {
|
|
57
68
|
break;
|
|
58
69
|
}
|
|
59
|
-
const syncedForFile = syncMountedFileBack(sourceFile, realMountDir, realProjectDir, readonlyMatcher, ignoredMatcher);
|
|
70
|
+
const syncedForFile = syncMountedFileBack(sourceFile, realMountDir, realProjectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher);
|
|
60
71
|
synced += syncedForFile;
|
|
61
72
|
if (signal && syncedForFile > 0 && !signal.aborted) {
|
|
62
73
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -107,7 +118,7 @@ function assertMountDirSafeToRemove(mountDir, projectDir) {
|
|
|
107
118
|
const markerPath = path.join(resolved, MOUNT_MARKER_FILENAME);
|
|
108
119
|
if (!existsSync(markerPath)) {
|
|
109
120
|
throw new Error(`Refusing to remove ${resolved}: missing ${MOUNT_MARKER_FILENAME} marker. ` +
|
|
110
|
-
`Only directories previously created by
|
|
121
|
+
`Only directories previously created by createMount can be reused as mountDir.`);
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
function walkProjectTree(projectDir, currentDir, mountDir, excludeSet, readonlyMatcher, ignoredMatcher) {
|
|
@@ -242,8 +253,8 @@ function hasSameContent(left, right) {
|
|
|
242
253
|
return false;
|
|
243
254
|
}
|
|
244
255
|
}
|
|
245
|
-
function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher, ignoredMatcher) {
|
|
246
|
-
const relative = resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher);
|
|
256
|
+
function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher) {
|
|
257
|
+
const relative = resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher);
|
|
247
258
|
if (!relative)
|
|
248
259
|
return 0;
|
|
249
260
|
const safeTargetPath = resolveVerifiedSyncTarget(projectDir, relative);
|
|
@@ -255,7 +266,7 @@ function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher,
|
|
|
255
266
|
copyFileSync(sourceFile, safeTargetPath);
|
|
256
267
|
return 1;
|
|
257
268
|
}
|
|
258
|
-
function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher) {
|
|
269
|
+
function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher) {
|
|
259
270
|
const relative = path.relative(mountDir, sourceFile);
|
|
260
271
|
if (relative === '' || relative.startsWith('..'))
|
|
261
272
|
return null;
|
|
@@ -264,7 +275,9 @@ function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredM
|
|
|
264
275
|
return null;
|
|
265
276
|
if (relativePosix === MOUNT_MARKER_FILENAME)
|
|
266
277
|
return null;
|
|
267
|
-
if (isPathMatched(relative, readonlyMatcher) ||
|
|
278
|
+
if (isPathMatched(relative, readonlyMatcher) ||
|
|
279
|
+
isPathMatched(relative, ignoredMatcher) ||
|
|
280
|
+
isPathMatched(relative, noSyncBackMatcher))
|
|
268
281
|
return null;
|
|
269
282
|
try {
|
|
270
283
|
if (lstatSync(sourceFile).isSymbolicLink())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/local-mount",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Create a symlink/copy mount of a project directory with .agentignore/.agentreadonly semantics, then launch a CLI inside it and sync writable changes back on exit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/dist/symlink-mount.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type AutoSyncHandle, type AutoSyncOptions } from './auto-sync.js';
|
|
2
|
-
export interface SymlinkMountOptions {
|
|
3
|
-
ignoredPatterns: string[];
|
|
4
|
-
readonlyPatterns: string[];
|
|
5
|
-
excludeDirs: string[];
|
|
6
|
-
/**
|
|
7
|
-
* Optional agent name used in the _MOUNT_README.md "Agent:" line.
|
|
8
|
-
* If omitted, the doc uses a generic "agent" value.
|
|
9
|
-
*/
|
|
10
|
-
agentName?: string;
|
|
11
|
-
}
|
|
12
|
-
export interface SymlinkMountHandle {
|
|
13
|
-
mountDir: string;
|
|
14
|
-
syncBack(opts?: {
|
|
15
|
-
signal?: AbortSignal;
|
|
16
|
-
}): Promise<number>;
|
|
17
|
-
/**
|
|
18
|
-
* Start bidirectional auto-sync: watches both the mount and project trees
|
|
19
|
-
* via @parcel/watcher and runs a full reconcile every `scanIntervalMs`
|
|
20
|
-
* as a safety net. Returns a handle you must `stop()` before teardown.
|
|
21
|
-
*/
|
|
22
|
-
startAutoSync(opts?: AutoSyncOptions): AutoSyncHandle;
|
|
23
|
-
cleanup(): void;
|
|
24
|
-
}
|
|
25
|
-
export declare function createSymlinkMount(projectDir: string, mountDir: string, options: SymlinkMountOptions): SymlinkMountHandle;
|