Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
da9c15c
Project import generated by Copybara.
Sep 20, 2026
9bfb7ad
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 20, 2026
2e3ada5
Project import generated by Copybara.
Sep 20, 2026
c791e1b
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 20, 2026
1977eb7
chore: project endpoint from Mono a2e8b231b208
Sep 20, 2026
b8a08a0
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
ff85ac7
chore: project endpoint from Mono 9b559bc70e7d
Sep 21, 2026
6214513
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
6188d83
chore: project endpoint from Mono 9a003786d767
Sep 21, 2026
692ee44
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
a8e83c3
chore: project endpoint from Mono 511bd2305f5a
Sep 21, 2026
ba59fec
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
b0061c7
chore: project endpoint from Mono ad7b9df0b712
Sep 21, 2026
2656701
chore: project endpoint from Mono 4ab4845398fd
Sep 21, 2026
13786d3
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 22, 2026
f6b9091
chore: project endpoint from Mono 2df44a8283c0
Sep 22, 2026
8bd7af4
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
1bddf52
chore: project endpoint from Mono 19eb7fff08ad
Sep 23, 2026
79aef39
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
8b9551a
chore: project endpoint from Mono be57256872b9
Sep 23, 2026
8635f11
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
0f1b3ef
chore: project endpoint from Mono fc21676e9136
Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "endpoint",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "be57256872b9a0b021c1da667b7803f806e8f289",
"sourceSha": "fc21676e9136ce5ddca1260a75b4044e3c3b8bc3",
"destinationRepository": "dx-corp/endpoint",
"priorProjectedBase": "f906d29bae651484eca29bf35d5577b0919a50df",
"priorProjectedBase": "2243d823a7fbcbb69382c681cc7858bda33136d4",
"definitionDigest": "8068fb5528eff3a9256419584bb34a9722ea322c288ee4cfda088ff93fb60ec6",
"toolDigest": "898e8657d9153a2a51d7c283bf83bb3350b5d1e6",
"contentDigest": "306e18a97c82824dfe39bd5e021dfbf2fa6ca4f28b2eb200ed1224cc052615bc",
"contentDigest": "9f84e7f599492777fd805f10e8f2cab5c9d2772d1f68134bebfe089edadc7e20",
"publicationEligible": true
}
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions macos/Sources/MerlinMacOS/Inventory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,7 @@ func collectMacAgentDiscovery(homes: [String], systemBins: [String]) -> (clis: [
]
for (client, kind, relative, format) in assetDirs {
let directory = "\(home)/\(relative)"
var directoryInfo = stat()
guard lstat(directory, &directoryInfo) == 0, (directoryInfo.st_mode & mode_t(S_IFMT)) == mode_t(S_IFDIR) else { continue }
for entry in ((try? FileManager.default.contentsOfDirectory(atPath: directory)) ?? []).sorted().prefix(256) {
for entry in boundedAgentDirectoryEntries(directory) {
let path = "\(directory)/\(entry)"
var info = stat()
guard lstat(path, &info) == 0 else { continue }
Expand Down Expand Up @@ -308,6 +306,21 @@ func collectMacAgentDiscovery(homes: [String], systemBins: [String]) -> (clis: [
return (clis, servers, assets)
}

private func boundedAgentDirectoryEntries(_ path: String) -> [String] {
let fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK)
guard fd >= 0 else { return [] }
guard let directory = fdopendir(fd) else { close(fd); return [] }
defer { closedir(directory) }
var names = [String]()
while names.count < 256, let entry = readdir(directory) {
let name = withUnsafePointer(to: &entry.pointee.d_name) { pointer in
pointer.withMemoryRebound(to: CChar.self, capacity: 1) { String(cString: $0) }
}
if name != "." && name != ".." { names.append(name) }
}
return names.sorted()
}

private func safeAgentAssetName(_ name: String) -> Bool {
!name.isEmpty && name.utf8.count <= 128 && !name.hasPrefix(".") &&
!name.contains("/") && !name.contains("\\") &&
Expand Down
3 changes: 3 additions & 0 deletions macos/Tests/MerlinMacOSTests/SyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ struct SyncTests {
try FileManager.default.createDirectory(atPath: home + "/.cursor", withIntermediateDirectories: true)
try FileManager.default.createDirectory(atPath: home + "/.local/bin", withIntermediateDirectories: true)
try FileManager.default.createDirectory(atPath: home + "/.agents/skills/review", withIntermediateDirectories: true)
try FileManager.default.createDirectory(atPath: home + "/.pi/agent", withIntermediateDirectories: true)
try FileManager.default.createSymbolicLink(atPath: home + "/.pi/agent/skills", withDestinationPath: home + "/.agents/skills")
try FileManager.default.createDirectory(atPath: home + "/.gemini/extensions/workspace", withIntermediateDirectories: true)
try FileManager.default.createDirectory(atPath: home + "/.gemini/extensions/not-extension", withIntermediateDirectories: true)
try FileManager.default.createDirectory(atPath: home + "/.claude/agents", withIntermediateDirectories: true)
Expand Down Expand Up @@ -170,6 +172,7 @@ struct SyncTests {
#expect(discovered.assets.contains { $0.client == "opencode" && $0.kind == "plugin" && $0.name == "trace" })
#expect(!discovered.assets.contains { $0.name == "off@marketplace" })
#expect(!discovered.assets.contains { $0.name == "not-extension" })
#expect(!discovered.assets.contains { $0.client == "pi" && $0.name == "review" })
let encoded = try JSONEncoder().encode(discovered.servers)
#expect(!String(decoding: encoded, as: UTF8.self).contains("secret"))
#expect(!String(decoding: try JSONEncoder().encode(discovered.assets), as: UTF8.self).contains("secret"))
Expand Down
8 changes: 8 additions & 0 deletions merlin/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2283,6 +2283,9 @@ mod tests {
"secret instructions",
)
.unwrap();
fs::create_dir_all(home.join(".pi/agent")).unwrap();
std::os::unix::fs::symlink(home.join(".agents/skills"), home.join(".pi/agent/skills"))
.unwrap();
fs::create_dir_all(home.join(".gemini/extensions/workspace")).unwrap();
fs::write(
home.join(".gemini/extensions/workspace/gemini-extension.json"),
Expand Down Expand Up @@ -2365,6 +2368,11 @@ mod tests {
&& item.name == "trace"));
assert!(!assets.iter().any(|item| item.name == "off@marketplace"));
assert!(!assets.iter().any(|item| item.name == "not-extension"));
assert!(
!assets
.iter()
.any(|item| item.client == "pi" && item.name == "review")
);
assert!(!serde_json::to_string(&assets).unwrap().contains("secret"));
fs::remove_file(home.join(".cursor/mcp.json")).unwrap();
std::os::unix::fs::symlink(
Expand Down