vexp-cli 3.3.0 → 3.3.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.
- package/dist/agent-config.js +3 -2
- package/dist/cloud-sync.js +98 -0
- package/dist/doctor.js +856 -21
- package/mcp/mcp-server.cjs +40 -40
- package/package.json +6 -6
package/dist/agent-config.js
CHANGED
|
@@ -2074,8 +2074,9 @@ export function configureZcodeMcp(workspaceRoot, binaryPath) {
|
|
|
2074
2074
|
export function zcodeUserConfigPath() {
|
|
2075
2075
|
return path.join(os.homedir(), ".zcode", "cli", "config.json");
|
|
2076
2076
|
}
|
|
2077
|
-
/** Does this UserPromptSubmit entry belong to us? Ours runs the vexp binary's `prompt-hint`.
|
|
2078
|
-
|
|
2077
|
+
/** Does this UserPromptSubmit entry belong to us? Ours runs the vexp binary's `prompt-hint`.
|
|
2078
|
+
* Exported for doctor, which must recognise the entry exactly as setup does. */
|
|
2079
|
+
export function isVexpZcodeHookEntry(h) {
|
|
2079
2080
|
if (!h || typeof h !== "object")
|
|
2080
2081
|
return false;
|
|
2081
2082
|
const hks = h.hooks;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud-synced workspace detection for doctor.
|
|
3
|
+
*
|
|
4
|
+
* Field report (2026-09-28, macOS, ZCode): the project lived under
|
|
5
|
+
* ~/Library/Mobile Documents/com~apple~CloudDocs, i.e. in iCloud Drive.
|
|
6
|
+
* Every file there is uploaded as it changes, .vexp/index.db included: a live
|
|
7
|
+
* SQLite database (plus its -wal and -shm) that the daemon rewrites on every
|
|
8
|
+
* index pass and every ledger row. On one machine that is sync traffic for
|
|
9
|
+
* nothing; opened from a second machine the sync client merges nothing and
|
|
10
|
+
* leaves "index (conflicted copy).db" files, or a database whose WAL and main
|
|
11
|
+
* file come from two different writers. Nothing in doctor said where the
|
|
12
|
+
* project was. Pure: the caller passes the platform, home and environment,
|
|
13
|
+
* so every rule is testable on any host.
|
|
14
|
+
*/
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
/** The provider macOS names its File Provider folders after
|
|
17
|
+
* (~/Library/CloudStorage/<Provider>-<account>). */
|
|
18
|
+
const CLOUD_STORAGE_PROVIDERS = [
|
|
19
|
+
[/^dropbox/i, "Dropbox"],
|
|
20
|
+
[/^onedrive/i, "OneDrive"],
|
|
21
|
+
[/^googledrive/i, "Google Drive"],
|
|
22
|
+
[/^box/i, "Box"],
|
|
23
|
+
];
|
|
24
|
+
/** `dir` itself or a folder under it; case-folded where the platform's
|
|
25
|
+
* default filesystem is (APFS/HFS+ on macOS, NTFS on Windows). */
|
|
26
|
+
function isUnder(child, dir, platform) {
|
|
27
|
+
const p = platform === "win32" ? path.win32 : path.posix;
|
|
28
|
+
const fold = platform === "win32" || platform === "darwin";
|
|
29
|
+
const a = fold ? p.resolve(dir).toLowerCase() : p.resolve(dir);
|
|
30
|
+
const b = fold ? p.resolve(child).toLowerCase() : p.resolve(child);
|
|
31
|
+
const rel = p.relative(a, b);
|
|
32
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(".." + p.sep) && !p.isAbsolute(rel));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The sync service that holds `root`, with the synced folder it was found
|
|
36
|
+
* under, or null. Only folders whose whole content a sync client uploads:
|
|
37
|
+
* iCloud Drive and the macOS File Provider folders, the OneDrive folders
|
|
38
|
+
* Windows exports as environment variables, and ~/Dropbox anywhere.
|
|
39
|
+
*/
|
|
40
|
+
export function cloudSyncProvider(root, ctx) {
|
|
41
|
+
const p = ctx.platform === "win32" ? path.win32 : path.posix;
|
|
42
|
+
if (!root || !ctx.home)
|
|
43
|
+
return null;
|
|
44
|
+
if (ctx.platform === "darwin") {
|
|
45
|
+
const icloud = p.join(ctx.home, "Library", "Mobile Documents");
|
|
46
|
+
if (isUnder(root, icloud, ctx.platform)) {
|
|
47
|
+
return { provider: "iCloud Drive", folder: icloud };
|
|
48
|
+
}
|
|
49
|
+
// With "Desktop & Documents Folders" on, com~apple~CloudDocs/Desktop
|
|
50
|
+
// and /Documents are symlinks to the real ~/Desktop and ~/Documents, so
|
|
51
|
+
// a project there is spelled ~/Desktop/... by process.cwd() and by
|
|
52
|
+
// realpath alike, and never matches the Mobile Documents prefix (the
|
|
53
|
+
// field report's path went through CloudDocs/Desktop, 2026-09-28).
|
|
54
|
+
for (const dir of ctx.icloudLinked ?? []) {
|
|
55
|
+
if (dir && isUnder(root, dir, ctx.platform))
|
|
56
|
+
return { provider: "iCloud Drive", folder: dir };
|
|
57
|
+
}
|
|
58
|
+
const storage = p.join(ctx.home, "Library", "CloudStorage");
|
|
59
|
+
// The first component under CloudStorage is the provider's folder;
|
|
60
|
+
// CloudStorage itself is only their parent.
|
|
61
|
+
// Sliced, not path.relative'd: the case-folded match above may have
|
|
62
|
+
// matched a root spelled in another case than home.
|
|
63
|
+
const first = isUnder(root, storage, ctx.platform) ? p.resolve(root).slice(p.resolve(storage).length + 1).split(p.sep)[0] : "";
|
|
64
|
+
if (first) {
|
|
65
|
+
const known = CLOUD_STORAGE_PROVIDERS.find(([re]) => re.test(first));
|
|
66
|
+
const provider = known ? known[1] : first.split("-")[0] || "a cloud storage provider";
|
|
67
|
+
return { provider, folder: p.join(storage, first) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (ctx.platform === "win32") {
|
|
71
|
+
for (const key of ["OneDrive", "OneDriveConsumer", "OneDriveCommercial"]) {
|
|
72
|
+
const dir = envValue(ctx.env, key);
|
|
73
|
+
if (dir && p.isAbsolute(dir) && isUnder(root, dir, ctx.platform))
|
|
74
|
+
return { provider: "OneDrive", folder: dir };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const dropbox = p.join(ctx.home, "Dropbox");
|
|
78
|
+
if (isUnder(root, dropbox, ctx.platform))
|
|
79
|
+
return { provider: "Dropbox", folder: dropbox };
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** Windows environment keys are case-insensitive; a plain object here is not. */
|
|
83
|
+
function envValue(env, key) {
|
|
84
|
+
if (env[key])
|
|
85
|
+
return env[key];
|
|
86
|
+
const hit = Object.keys(env).find((k) => k.toLowerCase() === key.toLowerCase());
|
|
87
|
+
return hit ? env[hit] : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** The WARN text for a workspace inside a synced folder; null otherwise. */
|
|
90
|
+
export function cloudSyncNotice(root, ctx) {
|
|
91
|
+
const hit = cloudSyncProvider(root, ctx);
|
|
92
|
+
if (!hit)
|
|
93
|
+
return null;
|
|
94
|
+
return (`workspace is inside ${hit.provider} (${hit.folder}), which syncs every file in it, including .vexp/index.db: ` +
|
|
95
|
+
"a live SQLite database vexp rewrites continuously. On one machine this costs sync traffic; opened from a second " +
|
|
96
|
+
"machine, the sync client can produce conflicted copies of the database. Move the project out of the synced folder " +
|
|
97
|
+
"(e.g. ~/Repos) and run 'vexp setup' there.");
|
|
98
|
+
}
|