@@ -36,8 +36,11 @@ import {
3636 checkSafeTarget ,
3737 executeFileOps ,
3838 executeUninstallOps ,
39+ listExtensionBundleManifests ,
40+ listJsonFiles ,
3941 readExistingFiles ,
4042} from "../util/install-fs.ts" ;
43+ import { claudeDesktopConfigPath } from "../detect/claude-desktop.ts" ;
4144import { binDir , home , isWin } from "../util/paths.ts" ;
4245import { mintAttestedSession , type AttestedTier } from "../util/session-mint.ts" ;
4346
@@ -159,15 +162,22 @@ export async function runInstall(argv: string[] = []): Promise<void> {
159162 // explicitly avoids the host-vs-container path mismatch. When --config-
160163 // dir is set, mirror it for every harness so the legacy flag's "single
161164 // dir wins" behavior is preserved on both sides.
165+ // Claude Desktop's config sits under platform-specific Application
166+ // Support / APPDATA dirs, not under a "~/.claude" sibling. Resolve via
167+ // the detector helper so dev mode (AGENTLOCK_DEV_HOME) and real-host
168+ // mode share one source of truth for the path.
169+ const claudeDesktopDir = resolve ( join ( claudeDesktopConfigPath ( ) , ".." ) ) ;
162170 const hostConfigDirs : Record < string , string > = flags . configDirOverride
163171 ? {
164172 "claude-code" : flags . configDirOverride ,
173+ "claude-desktop" : flags . configDirOverride ,
165174 codex : flags . configDirOverride ,
166175 cursor : flags . configDirOverride ,
167176 gemini : flags . configDirOverride ,
168177 }
169178 : {
170179 "claude-code" : resolve ( join ( home ( ) , ".claude" ) ) ,
180+ "claude-desktop" : claudeDesktopDir ,
171181 codex : resolve ( join ( home ( ) , ".codex" ) ) ,
172182 cursor : resolve ( join ( home ( ) , ".cursor" ) ) ,
173183 gemini : resolve ( join ( home ( ) , ".gemini" ) ) ,
@@ -177,7 +187,11 @@ export async function runInstall(argv: string[] = []): Promise<void> {
177187 const devMode = ! ! process . env . AGENTLOCK_DEV_HOME ;
178188 const results = await detectAll ( ) ;
179189 const isMvpEnabled = ( id : HarnessId ) : boolean =>
180- id === "claude-code" || id === "codex" || id === "cursor" || id === "gemini" ;
190+ id === "claude-code" ||
191+ id === "claude-desktop" ||
192+ id === "codex" ||
193+ id === "cursor" ||
194+ id === "gemini" ;
181195 const options = results . map ( ( r ) => {
182196 const enabled = devMode || isMvpEnabled ( r . id ) ;
183197 let sub : string ;
@@ -337,6 +351,13 @@ export async function runInstall(argv: string[] = []): Promise<void> {
337351 if ( ! dir ) continue ;
338352 if ( id === "claude-code" ) {
339353 uninstallPaths . push ( resolve ( join ( dir , "settings.json" ) ) ) ;
354+ } else if ( id === "claude-desktop" ) {
355+ uninstallPaths . push ( resolve ( join ( dir , "claude_desktop_config.json" ) ) ) ;
356+ // Bundle manifests live one dir over and are the actual launch
357+ // source for Desktop Extensions — the daemon needs each to
358+ // unwind the wrap on uninstall.
359+ const bundlesDir = resolve ( join ( dir , "Claude Extensions" ) ) ;
360+ uninstallPaths . push ( ...( await listExtensionBundleManifests ( bundlesDir ) ) ) ;
340361 } else if ( id === "codex" || id === "cursor" ) {
341362 uninstallPaths . push ( resolve ( join ( dir , "hooks.json" ) ) ) ;
342363 } else if ( id === "gemini" ) {
@@ -388,18 +409,40 @@ export async function runInstall(argv: string[] = []): Promise<void> {
388409 const claudeSettings = resolve (
389410 join ( hostConfigDirs [ "claude-code" ] , "settings.json" ) ,
390411 ) ;
412+ const claudeDesktopConfig = resolve (
413+ join ( hostConfigDirs [ "claude-desktop" ] , "claude_desktop_config.json" ) ,
414+ ) ;
391415 const codexHooks = resolve ( join ( hostConfigDirs [ "codex" ] , "hooks.json" ) ) ;
392416 const codexConfig = resolve ( join ( hostConfigDirs [ "codex" ] , "config.toml" ) ) ;
393417 const cursorHooks = resolve ( join ( hostConfigDirs [ "cursor" ] , "hooks.json" ) ) ;
394418 const geminiSettings = resolve (
395419 join ( hostConfigDirs [ "gemini" ] , "settings.json" ) ,
396420 ) ;
421+ // Per-extension bundle manifests are THE launch source for Desktop
422+ // Extensions installed via Settings → Extensions UI — claudeDesktopPlan
423+ // wraps each one in place using the schema-blessed _meta.agentlock
424+ // slot (MCPB v0.3+). The Claude Extensions Settings sidecar JSONs
425+ // tell us which extensions are isEnabled so disabled ones get
426+ // unwound rather than re-wrapped.
427+ const claudeDesktopBundlesDir = resolve (
428+ join ( hostConfigDirs [ "claude-desktop" ] , "Claude Extensions" ) ,
429+ ) ;
430+ const claudeDesktopExtSettingsDir = resolve (
431+ join ( hostConfigDirs [ "claude-desktop" ] , "Claude Extensions Settings" ) ,
432+ ) ;
433+ const bundleManifests = await listExtensionBundleManifests (
434+ claudeDesktopBundlesDir ,
435+ ) ;
436+ const extSettingsFiles = await listJsonFiles ( claudeDesktopExtSettingsDir ) ;
397437 const existingFiles = await readExistingFiles ( [
398438 claudeSettings ,
439+ claudeDesktopConfig ,
399440 codexHooks ,
400441 codexConfig ,
401442 cursorHooks ,
402443 geminiSettings ,
444+ ...bundleManifests ,
445+ ...extSettingsFiles ,
403446 ] ) ;
404447
405448 // Write the status-line script alongside the binary wrapper. Daemon
0 commit comments