Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
96fd57a
feat(mcp): remote MCP over Streamable HTTP for web chats (ChatGPT + C…
rrader2890 Aug 8, 2026
7cb739b
docs(mcp): remote MCP runbook for ChatGPT + Claude.ai web connectors
rrader2890 Aug 8, 2026
6834014
feat(install): wire SessionStart recall hook (not just capture)
rrader2890 Aug 8, 2026
de4d6ba
fix(observe): extract prompt from AI-tool hook JSON envelope on stdin
rrader2890 Aug 8, 2026
ebd2770
fix(memory): filter harness noise on capture + importance-rank sessio…
rrader2890 Aug 8, 2026
8f0c4e2
feat(extraction): detect ideas/aspirations as first-class 'idea' type
rrader2890 Aug 9, 2026
336dc06
feat(console): Ideas tab — browse/add/archive conversation-derived ideas
rrader2890 Aug 9, 2026
7067900
fix(security): never persist credential-bearing text (safety net)
rrader2890 Aug 9, 2026
3d948e3
feat(vault): encrypted secrets vault + 'memmesh secret' CLI (execute-…
rrader2890 Aug 9, 2026
ead07c8
feat(secrets): MCP tools + console Vault tab (AI references, never re…
rrader2890 Aug 9, 2026
004c177
feat(vault): console Retrieve (human reveal/copy) + AI credential-pro…
rrader2890 Aug 9, 2026
7b883b3
docs(skill): teach AI tools to route credentials through the vault
rrader2890 Aug 10, 2026
f6a0370
feat(console): focused credential-entry panel for the AI-prompt flow
rrader2890 Aug 10, 2026
bca99d3
feat(hook): enforcing PreToolUse secret-guard (deterministic vault re…
rrader2890 Aug 10, 2026
2fa1c1a
docs(enterprise): edge spec — enforce (guard/vault/redact/audit) + ph…
rrader2890 Aug 10, 2026
231eec9
feat(vault): typed credential entry forms (the right screen per kind)
rrader2890 Aug 10, 2026
0bfef9d
feat(desktop): native MemMesh desktop app (engine + console, one binary)
rrader2890 Aug 10, 2026
431523d
docs(enterprise): desktop spec — OSS/Enterprise tiers + two phone-hom…
rrader2890 Aug 10, 2026
28c3ba1
feat(integrations): MemMesh memory provider for Hermes Agent
rrader2890 Aug 25, 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
2,117 changes: 1,980 additions & 137 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"crates/license",
"crates/eval",
"crates/cli",
"crates/desktop",
]

[workspace.package]
Expand All @@ -37,6 +38,12 @@ futures = "0.3"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
tracing-appender = "0.2"
# Secrets vault (encrypted credential storage)
chacha20poly1305 = "0.10"
keyring = { version = "3", features = ["apple-native", "sync-secret-service", "windows-native"] }
zeroize = "1"
rand = "0.8"
rpassword = "7"

# Serialization
serde = { version = "1", features = ["derive"] }
Expand Down
1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tracing-appender.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
rpassword.workspace = true
uuid.workspace = true
chrono.workspace = true
toml.workspace = true
Expand Down
93 changes: 59 additions & 34 deletions crates/cli/src/installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,36 +305,67 @@ fn install_claude_code_hook(settings_path: &Path, binary: &Path) -> Result<()> {
bail!("{}::hooks is not an object", settings_path.display());
}
let hooks_obj = hooks.as_object_mut().unwrap();
let bin = binary.to_string_lossy();

// Our hook entry — matcher "*" catches every prompt. Command runs
// synchronously (Claude Code waits for it), reads the prompt from
// stdin, and is heuristic-only so finishes in <50ms even on a cold
// start. We pipe stderr to /dev/null to keep Claude Code's output
// clean if extraction fails.
let our_hook = serde_json::json!({
// 1. CAPTURE — UserPromptSubmit: pipe every prompt into the engine.
// matcher "*" catches every prompt; runs synchronously (Claude Code
// waits), heuristic-only so it finishes in <50ms even cold. stderr is
// dropped so a failure never dirties Claude Code's output.
let observe_hook = serde_json::json!({
"matcher": "*",
"hooks": [
{
"type": "command",
"command": format!(
"{} observe --role user --json 2>/dev/null || true",
binary.to_string_lossy(),
)
}
]
"hooks": [ {
"type": "command",
"command": format!("{bin} observe --role user --json 2>/dev/null || true"),
} ]
});
upsert_owned_hook(hooks_obj, "UserPromptSubmit", observe_hook, "memmesh observe")?;

// 2. RECALL — SessionStart: inject relevant memories into context at the
// start of each session. The hook's stdout is added to the session
// context, so `search --format claude-context` surfaces the memory
// block automatically (no dependency on the model choosing to search).
let recall_hook = serde_json::json!({
"hooks": [ {
"type": "command",
"command": format!("{bin} search --format claude-context --limit 30 2>/dev/null || true"),
} ]
});
upsert_owned_hook(hooks_obj, "SessionStart", recall_hook, "memmesh search")?;

// 3. ENFORCE — PreToolUse secret-guard on Bash: block a command that
// carries a live credential and redirect it to the vault. Deterministic
// enforcement the advisory skill can't guarantee — secrets can't reach a
// shell command or the transcript. Fast (regex only, no engine init).
let guard_hook = serde_json::json!({
"matcher": "Bash",
"hooks": [ {
"type": "command",
"command": format!("{bin} hook secret-guard"),
} ]
});
upsert_owned_hook(hooks_obj, "PreToolUse", guard_hook, "hook secret-guard")?;

let serialized = serde_json::to_string_pretty(&doc)?;
std::fs::write(settings_path, serialized)?;
Ok(())
}

// Append-or-replace logic: if there's already a hooks block we own
// (matcher "*" with a command containing "memmesh observe"),
// replace it; otherwise append to the array so other hooks survive.
let event = hooks_obj
.entry("UserPromptSubmit".to_string())
/// Append-or-replace a hook we own under `event`, identified by `marker`
/// appearing in one of its command strings. Other vendors' hooks are left
/// untouched, and re-running install replaces our entry rather than
/// duplicating it.
fn upsert_owned_hook(
hooks_obj: &mut serde_json::Map<String, serde_json::Value>,
event: &str,
our_hook: serde_json::Value,
marker: &str,
) -> Result<()> {
let ev = hooks_obj
.entry(event.to_string())
.or_insert_with(|| serde_json::json!([]));
let arr = event
let arr = ev
.as_array_mut()
.ok_or_else(|| anyhow!("hooks.UserPromptSubmit is not an array"))?;

let mut replaced = false;
.ok_or_else(|| anyhow!("hooks.{event} is not an array"))?;
for entry in arr.iter_mut() {
let is_ours = entry
.get("hooks")
Expand All @@ -343,22 +374,16 @@ fn install_claude_code_hook(settings_path: &Path, binary: &Path) -> Result<()> {
hs.iter().any(|h| {
h.get("command")
.and_then(|c| c.as_str())
.is_some_and(|s| s.contains("memmesh observe"))
.is_some_and(|s| s.contains(marker))
})
})
.unwrap_or(false);
if is_ours {
*entry = our_hook.clone();
replaced = true;
break;
*entry = our_hook;
return Ok(());
}
}
if !replaced {
arr.push(our_hook);
}

let serialized = serde_json::to_string_pretty(&doc)?;
std::fs::write(settings_path, serialized)?;
arr.push(our_hook);
Ok(())
}

Expand Down
Loading
Loading