Skip to content

Commit af167f0

Browse files
authored
Merge pull request #80 from openagentlock/feat/claude-desktop
Feat/claude desktop
2 parents 36d245f + ee42c59 commit af167f0

25 files changed

Lines changed: 3559 additions & 19 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ agentlock install --tier totp --code 123456 --passphrase 'your-passphrase-here'
4646

4747
For a quick eval without a signer (dev only): start the daemon with `-e AGENTLOCK_ALLOW_UNATTESTED=1`, then `agentlock install` (defaults to unattested).
4848

49-
Open the local web dashboard at <http://127.0.0.1:7879/>.
49+
Open the local web dashboard at <http://127.0.0.1:7879/>, or run `agentlock dashboard` for a terminal TUI with the same live ledger tail, sessions, loaded gates, and a one-key monitor⇄enforce flip.
5050

5151
Full walkthrough at <https://openagentlock.github.io/OpenAgentLock/guide/getting-started/>.
5252

cli/src/commands/install.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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";
4144
import { binDir, home, isWin } from "../util/paths.ts";
4245
import { 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

Comments
 (0)