Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ agentlock install --tier totp --code 123456 --passphrase 'your-passphrase-here'

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

Open the local web dashboard at <http://127.0.0.1:7879/>.
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.

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

Expand Down
45 changes: 44 additions & 1 deletion cli/src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ import {
checkSafeTarget,
executeFileOps,
executeUninstallOps,
listExtensionBundleManifests,
listJsonFiles,
readExistingFiles,
} from "../util/install-fs.ts";
import { claudeDesktopConfigPath } from "../detect/claude-desktop.ts";
import { binDir, home, isWin } from "../util/paths.ts";
import { mintAttestedSession, type AttestedTier } from "../util/session-mint.ts";

Expand Down Expand Up @@ -159,15 +162,22 @@ export async function runInstall(argv: string[] = []): Promise<void> {
// explicitly avoids the host-vs-container path mismatch. When --config-
// dir is set, mirror it for every harness so the legacy flag's "single
// dir wins" behavior is preserved on both sides.
// Claude Desktop's config sits under platform-specific Application
// Support / APPDATA dirs, not under a "~/.claude" sibling. Resolve via
// the detector helper so dev mode (AGENTLOCK_DEV_HOME) and real-host
// mode share one source of truth for the path.
const claudeDesktopDir = resolve(join(claudeDesktopConfigPath(), ".."));
const hostConfigDirs: Record<string, string> = flags.configDirOverride
? {
"claude-code": flags.configDirOverride,
"claude-desktop": flags.configDirOverride,
codex: flags.configDirOverride,
cursor: flags.configDirOverride,
gemini: flags.configDirOverride,
}
: {
"claude-code": resolve(join(home(), ".claude")),
"claude-desktop": claudeDesktopDir,
codex: resolve(join(home(), ".codex")),
cursor: resolve(join(home(), ".cursor")),
gemini: resolve(join(home(), ".gemini")),
Expand All @@ -177,7 +187,11 @@ export async function runInstall(argv: string[] = []): Promise<void> {
const devMode = !!process.env.AGENTLOCK_DEV_HOME;
const results = await detectAll();
const isMvpEnabled = (id: HarnessId): boolean =>
id === "claude-code" || id === "codex" || id === "cursor" || id === "gemini";
id === "claude-code" ||
id === "claude-desktop" ||
id === "codex" ||
id === "cursor" ||
id === "gemini";
const options = results.map((r) => {
const enabled = devMode || isMvpEnabled(r.id);
let sub: string;
Expand Down Expand Up @@ -337,6 +351,13 @@ export async function runInstall(argv: string[] = []): Promise<void> {
if (!dir) continue;
if (id === "claude-code") {
uninstallPaths.push(resolve(join(dir, "settings.json")));
} else if (id === "claude-desktop") {
uninstallPaths.push(resolve(join(dir, "claude_desktop_config.json")));
// Bundle manifests live one dir over and are the actual launch
// source for Desktop Extensions — the daemon needs each to
// unwind the wrap on uninstall.
const bundlesDir = resolve(join(dir, "Claude Extensions"));
uninstallPaths.push(...(await listExtensionBundleManifests(bundlesDir)));
} else if (id === "codex" || id === "cursor") {
uninstallPaths.push(resolve(join(dir, "hooks.json")));
} else if (id === "gemini") {
Expand Down Expand Up @@ -388,18 +409,40 @@ export async function runInstall(argv: string[] = []): Promise<void> {
const claudeSettings = resolve(
join(hostConfigDirs["claude-code"], "settings.json"),
);
const claudeDesktopConfig = resolve(
join(hostConfigDirs["claude-desktop"], "claude_desktop_config.json"),
);
const codexHooks = resolve(join(hostConfigDirs["codex"], "hooks.json"));
const codexConfig = resolve(join(hostConfigDirs["codex"], "config.toml"));
const cursorHooks = resolve(join(hostConfigDirs["cursor"], "hooks.json"));
const geminiSettings = resolve(
join(hostConfigDirs["gemini"], "settings.json"),
);
// Per-extension bundle manifests are THE launch source for Desktop
// Extensions installed via Settings → Extensions UI — claudeDesktopPlan
// wraps each one in place using the schema-blessed _meta.agentlock
// slot (MCPB v0.3+). The Claude Extensions Settings sidecar JSONs
// tell us which extensions are isEnabled so disabled ones get
// unwound rather than re-wrapped.
const claudeDesktopBundlesDir = resolve(
join(hostConfigDirs["claude-desktop"], "Claude Extensions"),
);
const claudeDesktopExtSettingsDir = resolve(
join(hostConfigDirs["claude-desktop"], "Claude Extensions Settings"),
);
const bundleManifests = await listExtensionBundleManifests(
claudeDesktopBundlesDir,
);
const extSettingsFiles = await listJsonFiles(claudeDesktopExtSettingsDir);
const existingFiles = await readExistingFiles([
claudeSettings,
claudeDesktopConfig,
codexHooks,
codexConfig,
cursorHooks,
geminiSettings,
...bundleManifests,
...extSettingsFiles,
]);

// Write the status-line script alongside the binary wrapper. Daemon
Expand Down
Loading
Loading