memhouse 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/memhouse.js +270 -22
- package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +2 -2
- package/memhouse/delivery/AGENT-INSTALL.md +2 -2
- package/memhouse/delivery/PROMPT.md +15 -9
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
- package/memhouse/delivery/plugin/reference/HOUSE.md +154 -0
- package/memhouse/delivery/plugin/skills/access/SKILL.md +148 -0
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +29 -0
- package/memhouse/delivery/plugin/skills/house/SKILL.md +99 -0
- package/memhouse/delivery/plugin/skills/recall/SKILL.md +119 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +73 -194
- package/memhouse/flags.js +1 -0
- package/memhouse/share.js +64 -0
- package/package.json +1 -1
- package/memhouse/delivery/plugin/skills/ask/SKILL.md +0 -131
- package/memhouse/delivery/plugin/skills/hello/SKILL.md +0 -121
- package/memhouse/delivery/plugin/skills/invite/SKILL.md +0 -130
- package/memhouse/delivery/plugin/skills/search/SKILL.md +0 -186
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +0 -164
- package/memhouse/delivery/plugin/skills/share/SKILL.md +0 -130
- package/memhouse/delivery/plugin/skills/status/SKILL.md +0 -139
- package/memhouse/delivery/plugin/skills/users/SKILL.md +0 -97
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: invite
|
|
3
|
-
description: Invite another person onto this memhouse ClickHouse — mint their user and their own house (database) with the right grants, producing the one env file their install needs. Use when the user says "invite X", "add X to memhouse", "get my friend set up". Inviting comes BEFORE sharing — /mem:share can only grant a user who exists.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
argument-hint: "<name> [into <db>]"
|
|
6
|
-
allowed-tools: Bash
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# /mem:invite — mint a member and hand them one file
|
|
10
|
-
|
|
11
|
-
This runs the `memhouse invite` CLI and nothing else. Inviting mints a ClickHouse user
|
|
12
|
-
and a house, which takes an ADMIN credential — and an admin password must never enter
|
|
13
|
-
this conversation, because memhouse ships the transcript into the house it just
|
|
14
|
-
provisioned. The CLI is the only safe path: the admin password is passed to one process
|
|
15
|
-
and never echoed. **Do not hand-run `CREATE USER` / `GRANT` with an admin password
|
|
16
|
-
substituted into a command — that burns the credential into the transcript on disk.**
|
|
17
|
-
|
|
18
|
-
## Do this
|
|
19
|
-
|
|
20
|
-
1. Confirm the `memhouse` binary is present: `command -v memhouse`. If it is not, tell
|
|
21
|
-
the user to install it first (`npm install -g memhouse`) — there is no in-skill
|
|
22
|
-
fallback, on purpose (see above).
|
|
23
|
-
|
|
24
|
-
2. Gather ONLY two things by asking:
|
|
25
|
-
- the invitee's **name** (validate `[A-Za-z][A-Za-z0-9_]*`; refuse otherwise)
|
|
26
|
-
- the **URL the invitee will reach the house at** — NOT localhost (that is the
|
|
27
|
-
invitee's own machine). A LAN IP, hostname, or tunnel.
|
|
28
|
-
|
|
29
|
-
Do NOT ask for an admin user or password yet — the next step usually needs neither.
|
|
30
|
-
Only ask about a **shared house** if the user brings one up; default is the invitee's
|
|
31
|
-
own house (omit --db; never pass `--db default` for "the default house").
|
|
32
|
-
|
|
33
|
-
3. TRY IT BARE FIRST — run this via the Bash tool, from the directory the file should
|
|
34
|
-
land in (step 4). `memhouse invite` uses the caller's OWN configured credential when
|
|
35
|
-
it can manage users, which is the common case for whoever set the house up, so no
|
|
36
|
-
admin is needed:
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
memhouse invite <name> --url <url> [--db <shared-house>]
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
If it prints `invite written: …`, go to step 4 — you are done, and no admin secret
|
|
43
|
-
ever touched this conversation.
|
|
44
|
-
|
|
45
|
-
Otherwise read WHICH refusal came back; they need different answers.
|
|
46
|
-
|
|
47
|
-
**"cannot reach `<url>` …"** — nothing to do with privileges. The address is wrong,
|
|
48
|
-
or the house is bound to loopback (`memhouse deploy --local` binds to 127.0.0.1 on
|
|
49
|
-
purpose, so no LAN address reaches it and no invitee could either). Ask the user for
|
|
50
|
-
the address the invitee will actually use; do not retry with a guess.
|
|
51
|
-
|
|
52
|
-
**"`<user>` is a MEMBER of this ClickHouse, not an administrator"** — inviting needs
|
|
53
|
-
CREATE USER and CREATE DATABASE, which a member deliberately does not hold. Two cases,
|
|
54
|
-
and you must ask which one applies rather than assuming:
|
|
55
|
-
|
|
56
|
-
- **They run the ClickHouse.** They have the admin credential; memhouse never stores
|
|
57
|
-
it. Do NOT take that password in chat — memhouse archives this transcript. Hand them
|
|
58
|
-
the line to run THEMSELVES, and leave `--admin-password` OFF so it is prompted for,
|
|
59
|
-
keeping it out of both the process list and shell history:
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
memhouse invite <name> --url <url> [--db <shared-house>] --admin-user <admin>
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**You cannot run that line yourself.** The prompt needs a TTY and your Bash tool has
|
|
66
|
-
none, so it refuses with "no TTY to prompt on" — and the fix is NOT to add
|
|
67
|
-
`--admin-password`, which would put the credential in this transcript. The user runs
|
|
68
|
-
it in their own terminal; you wait for them to say it is done.
|
|
69
|
-
|
|
70
|
-
- **Somebody else runs it.** They cannot invite, and no flag changes that. Print the
|
|
71
|
-
statements for whoever administers the server — this contacts nothing and needs no
|
|
72
|
-
credential:
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
memhouse invite <name> --url <url> [--db <shared-house>] --print-sql
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
The output ends with the four `MEMHOUSE_*` lines the invitee needs. Those lines are
|
|
79
|
-
a credential: tell the user to send them over a trusted channel, and **never print
|
|
80
|
-
them into the conversation yourself**.
|
|
81
|
-
|
|
82
|
-
**If it refuses with "house `<db>` already exists and holds N messages", STOP.** That
|
|
83
|
-
house is somebody's memory, and inviting into it would hand the invitee someone else's
|
|
84
|
-
sessions. Do not pass `--adopt` on your own initiative — report the row count and the
|
|
85
|
-
writer count to the user and let them choose: a different handle, a different `--db`,
|
|
86
|
-
or `--adopt` if sharing that house is genuinely what they want.
|
|
87
|
-
|
|
88
|
-
4. **Put the file where the user can attach it.** The CLI writes `invite-<name>.env`
|
|
89
|
-
into the CURRENT directory, and the user has to send it by hand — so run the command
|
|
90
|
-
from their downloads directory (on macOS and most Linux desktops `~/Downloads`), or
|
|
91
|
-
move it there afterwards. Never leave it in a git repo: it is a password, and
|
|
92
|
-
`invite-*.env` is only gitignored in this repo, not in whatever checkout the user
|
|
93
|
-
happened to be standing in.
|
|
94
|
-
|
|
95
|
-
Confirm it is there and still mode `600`, and say the full path. **Never print the
|
|
96
|
-
file's contents** — the password inside would land in a transcript memhouse ships.
|
|
97
|
-
|
|
98
|
-
5. **Say whether the house was fresh.** Run
|
|
99
|
-
`memhouse stats` or query `SELECT count() FROM <db>.messages` and tell the user
|
|
100
|
-
plainly: a brand-new house reads 0. This is a second line of defence behind step 3's
|
|
101
|
-
refusal, and it is how the user learns the invitee is starting clean.
|
|
102
|
-
|
|
103
|
-
6. **Write the message the user will send.** They are about to hand over a file plus an
|
|
104
|
-
explanation, so compose it for them rather than making them write it. Put it in a
|
|
105
|
-
fenced block they can copy whole, and cover:
|
|
106
|
-
- what memhouse is, in a sentence or two — the invitee may never have heard of it
|
|
107
|
-
- that the attached `invite-<name>.env` IS a password: don't forward it, don't paste
|
|
108
|
-
it into chat
|
|
109
|
-
- `npm install -g memhouse` (Node 24+, no flags, nothing compiles), then
|
|
110
|
-
`memhouse install --env invite-<name>.env`
|
|
111
|
-
- that install OFFERS to change the password to one only they know, then deletes the
|
|
112
|
-
file — so they should say yes, because the inviter knows the current one
|
|
113
|
-
- `memhouse onboard` to start shipping, `memhouse status` to check it worked
|
|
114
|
-
- that their house is theirs alone; the inviter cannot read it unless they run
|
|
115
|
-
`/mem:share <inviter>`
|
|
116
|
-
|
|
117
|
-
If the invitee is REPLACING an existing setup (they were shipping under another
|
|
118
|
-
credential), add `memhouse ship --full` after the install — a plain incremental pass
|
|
119
|
-
skips sessions the new house has no record of, so their history would not follow them.
|
|
120
|
-
|
|
121
|
-
Then: once installed, sharing works both ways — `/mem:share <name>`.
|
|
122
|
-
|
|
123
|
-
## Never
|
|
124
|
-
|
|
125
|
-
Never create a ClickHouse user by running raw SQL with an admin password you were given
|
|
126
|
-
in chat. Never invent an admin credential. Never suggest granting beyond what `memhouse
|
|
127
|
-
invite` does. If the binary is absent, stop and say so — do not improvise the SQL.
|
|
128
|
-
|
|
129
|
-
Never print the invite file's contents, and never pass `--adopt` unless the user asked
|
|
130
|
-
for it after being told what is already in that house.
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: search
|
|
3
|
-
description: Full-text search across ALL shipped agent conversations — every session from all 17 supported editors (Claude Code, Codex, Cursor, VS Code, Zed, OpenCode, Gemini CLI, …), every project, every machine — stored in the memhouse ClickHouse. Use whenever the user refers to something from the past that isn't in the current context, e.g. "what did I say about X", "find that conversation about Y", "when did I work on Z", "did I ever try W", "the chat where we discussed it", "remind me how I did it". Searches your own house by default, or a friend's house shared with you when they name it ("find X in yigit's memory"). Reach for this before saying you don't know about prior work.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
argument-hint: "<search terms> [in <project>] [last <N> days] [from <editor>]"
|
|
6
|
-
allowed-tools: Bash
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# /mem:search — search conversation memory
|
|
10
|
-
|
|
11
|
-
Search the full message history in the memhouse house. Every room is named for your
|
|
12
|
-
ClickHouse user — `messages`, `sessions` — see **Room names** below.
|
|
13
|
-
|
|
14
|
-
## Connection
|
|
15
|
-
|
|
16
|
-
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no `env | grep
|
|
17
|
-
MEMHOUSE`, no `set -x` around these commands. Anything you print becomes part of a
|
|
18
|
-
transcript that memhouse itself ships into the house.
|
|
19
|
-
|
|
20
|
-
Credentials resolve as **flags > exported `MEMHOUSE_*` > `$MEMHOUSE_HOME/env`**
|
|
21
|
-
(default `~/.memhouse/env`) — the same order the `memhouse` CLI uses. Every query
|
|
22
|
-
runs over ClickHouse HTTP with `final=1` and `join_use_nulls=1` (ReplacingMergeTree
|
|
23
|
-
keeps stale row versions until merges; `final=1` collapses to latest-wins — always
|
|
24
|
-
include it on reads):
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
# Config lives at $MEMHOUSE_HOME/env (default ~/.memhouse/env). Exported MEMHOUSE_*
|
|
28
|
-
# vars WIN over the file — snapshot them, source, then put them back. Sourcing alone
|
|
29
|
-
# lets a stale file silently override the house you were pointed at.
|
|
30
|
-
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
31
|
-
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
32
|
-
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
33
|
-
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
34
|
-
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
35
|
-
# No default URL. localhost:8123 as memhouse_root is a REAL house on many machines,
|
|
36
|
-
# usually the pilot's own — guessing it reads someone else's memory and looks like it
|
|
37
|
-
# worked. If there is no config, say so and stop.
|
|
38
|
-
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
39
|
-
# An explicit test, not ${VAR:?msg}: zsh does not expand the message, so under the
|
|
40
|
-
# shell Claude Code actually uses the refusal read "nothing in $MH_ENV" literally.
|
|
41
|
-
echo "no memhouse house configured — nothing in $MH_ENV and no MEMHOUSE_URL/MEMHOUSE_USER set." >&2
|
|
42
|
-
echo "Run: memhouse install" >&2
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# --user, not a -K config file. The config-file parser treats `"` and `\` specially, so a
|
|
47
|
-
# password containing either authenticates fine from the CLI and fails from every skill
|
|
48
|
-
# with "password is incorrect" — measured. The argv exposure -K was meant to avoid is not
|
|
49
|
-
# observable here either: curl blanks the --user argument before `ps` can read it (0 hits
|
|
50
|
-
# in 50 samples). What actually leaks a credential is PRINTING it, which the rule above
|
|
51
|
-
# covers.
|
|
52
|
-
curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
53
|
-
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-$MEMHOUSE_USER}&final=1&join_use_nulls=1&readonly=1" <<'SQL'
|
|
54
|
-
<the query>
|
|
55
|
-
FORMAT PrettyCompact
|
|
56
|
-
SQL
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
`FORMAT PrettyCompact` for display; `FORMAT JSONEachRow` when you want to parse
|
|
60
|
-
the rows yourself.
|
|
61
|
-
|
|
62
|
-
## Room names — plain, shared tables
|
|
63
|
-
|
|
64
|
-
The house's rooms are three plain tables: `messages`, `sessions`, `tool_calls` — resolved
|
|
65
|
-
by the connection's database (`MEMHOUSE_DB`), not by who is asking. Everyone in the house
|
|
66
|
-
writes into the same tables; `user_id` (stamped by the server) says whose row it is and
|
|
67
|
-
`host` says which machine shipped it. Search the whole house by default; add
|
|
68
|
-
`AND user_id = '<name>'` only when the user asks for one person's sessions.
|
|
69
|
-
|
|
70
|
-
**Keep the SQL heredoc quoted (`<<'SQL'`).** This skill once told the agent to substitute
|
|
71
|
-
a shell variable into the table names, which cannot expand inside a quoted heredoc — so
|
|
72
|
-
the agent unquotes it, and then the SEARCH TERMS expand too. Both instances driving this
|
|
73
|
-
skill did exactly that, unprompted, on the first attempt. Measured consequences, from
|
|
74
|
-
`system.query_log`:
|
|
75
|
-
|
|
76
|
-
- a term containing `$home` became the empty string, so `LIKE '%%'` matched **every row**
|
|
77
|
-
and reported hits with exit 0 — silently wrong, not an error;
|
|
78
|
-
- a term containing a backtick **executed the command inside it**.
|
|
79
|
-
|
|
80
|
-
A user's search term is arbitrary text. With the heredoc quoted, neither can happen.
|
|
81
|
-
|
|
82
|
-
**There is no `sessions_v` object.** The session rollup is a saved query over those same
|
|
83
|
-
rooms — `memhouse sessions-query` prints it for whoever you are connected as, ready to
|
|
84
|
-
paste into a `FROM (...) AS c` position.
|
|
85
|
-
|
|
86
|
-
**The rollup is self-contained.** As of 0.4.5 the printed text carries its own `FINAL` on
|
|
87
|
-
both rooms and a trailing `SETTINGS join_use_nulls = 1`, so it is correct wherever you
|
|
88
|
-
paste it. `final=1` on the connection still matters for reads of the rooms THEMSELVES,
|
|
89
|
-
which carry no FINAL of their own.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
## How to search (the FTS columns)
|
|
93
|
-
|
|
94
|
-
`messages` carries two MATERIALIZED lowercase copies of `text`, each with a
|
|
95
|
-
text index — search those, display `text`:
|
|
96
|
-
|
|
97
|
-
- `text_ngram` (ngram index) → substring match: `text_ngram LIKE '%term%'`
|
|
98
|
-
- `text_word` (word index) → whole-token match: `hasToken(text_word, 'term')`
|
|
99
|
-
|
|
100
|
-
**Search terms MUST be lowercased** (the columns are `lower(text)`); escape
|
|
101
|
-
single quotes in user terms as `\'`. Multiple words → AND of clauses; a quoted
|
|
102
|
-
phrase → one `LIKE` clause for the whole phrase.
|
|
103
|
-
|
|
104
|
-
### Example 1 — substring search with session names
|
|
105
|
-
|
|
106
|
-
```sql
|
|
107
|
-
SELECT m.ts, m.source, m.project, m.role, m.session_id,
|
|
108
|
-
s.name AS session,
|
|
109
|
-
substring(m.text, 1, 300) AS snippet
|
|
110
|
-
FROM messages AS m
|
|
111
|
-
LEFT JOIN sessions AS s USING (session_id, user_id)
|
|
112
|
-
WHERE m.text_ngram LIKE '%postgres%' AND m.text_ngram LIKE '%migration%'
|
|
113
|
-
ORDER BY m.ts DESC
|
|
114
|
-
LIMIT 30
|
|
115
|
-
FORMAT PrettyCompact
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Example 2 — word match, scoped by editor and time
|
|
119
|
-
|
|
120
|
-
```sql
|
|
121
|
-
SELECT ts, project, session_id, substring(text, 1, 200) AS snippet
|
|
122
|
-
FROM messages
|
|
123
|
-
WHERE hasToken(text_word, 'rls')
|
|
124
|
-
AND source = 'claude-code'
|
|
125
|
-
AND ts > now() - INTERVAL 14 DAY
|
|
126
|
-
ORDER BY ts DESC
|
|
127
|
-
LIMIT 20
|
|
128
|
-
FORMAT PrettyCompact
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Example 3 — which sessions mention it most (then drill in)
|
|
132
|
-
|
|
133
|
-
```sql
|
|
134
|
-
SELECT m.session_id, m.user_id, any(s.name) AS session, any(m.project) AS project,
|
|
135
|
-
count() AS hits, max(m.ts) AS last_hit
|
|
136
|
-
FROM messages AS m
|
|
137
|
-
LEFT JOIN sessions AS s USING (session_id, user_id)
|
|
138
|
-
WHERE m.text_ngram LIKE '%clickhouse cache%'
|
|
139
|
-
GROUP BY m.session_id, m.user_id
|
|
140
|
-
ORDER BY hits DESC
|
|
141
|
-
LIMIT 10
|
|
142
|
-
FORMAT PrettyCompact
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Reading another person's house (shared with you)
|
|
146
|
-
|
|
147
|
-
Every house is its own ClickHouse database on the same server; a share is a read-only
|
|
148
|
-
GRANT on that database. To read a friend's memory you keep YOUR credentials and point the
|
|
149
|
-
connection at THEIR house — the rooms (`messages`, `sessions`, `tool_calls`) resolve by
|
|
150
|
-
the connection's `database`, so one change redirects every query in this skill.
|
|
151
|
-
|
|
152
|
-
1. **Resolve the target house.** `SHOW DATABASES` lists what your credential may read; the
|
|
153
|
-
names that are not `system` / `information_schema` / `default` and not your own
|
|
154
|
-
`$MEMHOUSE_DB` are houses shared with you. If the user names one — "yigit's memory",
|
|
155
|
-
"in yigit", "house yigit", "from yigit" — set `HOUSE=yigit`; otherwise leave
|
|
156
|
-
`HOUSE=$MEMHOUSE_DB`.
|
|
157
|
-
2. **Point the connection at it:** in the recipe above, replace
|
|
158
|
-
`database=${MEMHOUSE_DB:-$MEMHOUSE_USER}` with `database=<HOUSE>`. Leave the table names bare —
|
|
159
|
-
they resolve to that house. Nothing else in the query changes.
|
|
160
|
-
3. **A house wins over `in <project>`.** A name that matches a readable house is a HOUSE,
|
|
161
|
-
not a project filter — otherwise "in yigit" silently filters YOUR house's project
|
|
162
|
-
column and returns nothing. If a real project in your own house shares the name, keep
|
|
163
|
-
the possessive ("<name>'s memory") or say "project <name>" to force the project reading.
|
|
164
|
-
4. **Read-only, thin by design.** If the SELECT is refused, that house was not shared with
|
|
165
|
-
you (or the share was revoked) — say so; never guess another database.
|
|
166
|
-
|
|
167
|
-
## Qualifiers
|
|
168
|
-
|
|
169
|
-
- `in <project>` → `AND m.project ILIKE '%<project>%'` — but a name matching a readable
|
|
170
|
-
house is a HOUSE (see above), not a project.
|
|
171
|
-
- `last N days` → `AND m.ts > now() - INTERVAL N DAY`
|
|
172
|
-
- `from <editor>` → `AND m.source = '<id>'` (ids: `claude-code`, `codex`,
|
|
173
|
-
`cursor`, `cursor-agent`, `vscode`, `zed`, `opencode`, `gemini-cli`,
|
|
174
|
-
`windsurf`, `antigravity`, `copilot-cli`, `goose`, `kiro`, …)
|
|
175
|
-
- Skip subagent noise → `AND NOT m.is_subagent`
|
|
176
|
-
- Only user/assistant text lives in `messages`; tool invocations are in
|
|
177
|
-
`tool_calls` (`tool_name`, `args`) — search `args ILIKE '%term%'` there
|
|
178
|
-
only if the user explicitly wants tool calls searched.
|
|
179
|
-
|
|
180
|
-
## Output
|
|
181
|
-
|
|
182
|
-
Summarize hits grouped by session (name, source, project, date, 1–2 best
|
|
183
|
-
snippets each) and give the total hit count. Offer to pull the full transcript
|
|
184
|
-
of the best session (`SELECT role, text FROM messages WHERE session_id = '…'
|
|
185
|
-
ORDER BY seq`). If zero hits, retry with broader/fewer terms and report the
|
|
186
|
-
corpus size (`SELECT count() FROM messages`).
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sessions
|
|
3
|
-
description: List recent agent sessions from memhouse conversation memory — names, projects, editors, hosts, times, message and token counts across all 17 supported editors. Use for "what was I working on", "show my recent sessions", "sessions from last week", "what did I do in project X", "list my cursor sessions", or to orient before drilling into one session.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
argument-hint: "[N] [project <name>] [from <editor>] [today|week|month]"
|
|
6
|
-
allowed-tools: Bash
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# /mem:sessions — browse session history
|
|
10
|
-
|
|
11
|
-
List/filter sessions from the session rollup — a saved query over your own rooms, not an
|
|
12
|
-
object; `memhouse sessions-query` prints it (session metadata +
|
|
13
|
-
message aggregates) in the memhouse house.
|
|
14
|
-
|
|
15
|
-
## When to use
|
|
16
|
-
|
|
17
|
-
- The user wants an overview of recent activity rather than a specific quote.
|
|
18
|
-
- As a first step before digging into one session, when they don't have an
|
|
19
|
-
id/name yet.
|
|
20
|
-
|
|
21
|
-
## Connection
|
|
22
|
-
|
|
23
|
-
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no `env | grep
|
|
24
|
-
MEMHOUSE`, no `set -x` around these commands. Anything you print becomes part of a
|
|
25
|
-
transcript that memhouse itself ships into the house.
|
|
26
|
-
|
|
27
|
-
Credentials resolve as **flags > exported `MEMHOUSE_*` > `$MEMHOUSE_HOME/env`**
|
|
28
|
-
(default `~/.memhouse/env`) — the same order the `memhouse` CLI uses. Every query
|
|
29
|
-
runs over ClickHouse HTTP with `final=1` and `join_use_nulls=1` (ReplacingMergeTree
|
|
30
|
-
keeps stale row versions until merges; `final=1` collapses to latest-wins — always
|
|
31
|
-
include it on reads):
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Config lives at $MEMHOUSE_HOME/env (default ~/.memhouse/env). Exported MEMHOUSE_*
|
|
35
|
-
# vars WIN over the file — snapshot them, source, then put them back. Sourcing alone
|
|
36
|
-
# lets a stale file silently override the house you were pointed at.
|
|
37
|
-
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
38
|
-
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
39
|
-
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
40
|
-
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
41
|
-
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
42
|
-
# No default URL. localhost:8123 as memhouse_root is a REAL house on many machines,
|
|
43
|
-
# usually the pilot's own — guessing it reads someone else's memory and looks like it
|
|
44
|
-
# worked. If there is no config, say so and stop.
|
|
45
|
-
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
46
|
-
# An explicit test, not ${VAR:?msg}: zsh does not expand the message, so under the
|
|
47
|
-
# shell Claude Code actually uses the refusal read "nothing in $MH_ENV" literally.
|
|
48
|
-
echo "no memhouse house configured — nothing in $MH_ENV and no MEMHOUSE_URL/MEMHOUSE_USER set." >&2
|
|
49
|
-
echo "Run: memhouse install" >&2
|
|
50
|
-
exit 1
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# --user, not a -K config file. The config-file parser treats `"` and `\` specially, so a
|
|
54
|
-
# password containing either authenticates fine from the CLI and fails from every skill
|
|
55
|
-
# with "password is incorrect" — measured. The argv exposure -K was meant to avoid is not
|
|
56
|
-
# observable here either: curl blanks the --user argument before `ps` can read it (0 hits
|
|
57
|
-
# in 50 samples). What actually leaks a credential is PRINTING it, which the rule above
|
|
58
|
-
# covers.
|
|
59
|
-
curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
60
|
-
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-$MEMHOUSE_USER}&final=1&join_use_nulls=1&readonly=1" <<'SQL'
|
|
61
|
-
<the query>
|
|
62
|
-
FORMAT PrettyCompact
|
|
63
|
-
SQL
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Room names — plain, shared tables
|
|
67
|
-
|
|
68
|
-
The house's rooms are three plain tables: `messages`, `sessions`, `tool_calls` — resolved
|
|
69
|
-
by the connection's database (`MEMHOUSE_DB`), not by who is asking. Everyone in the house
|
|
70
|
-
writes into the same tables; `user_id` (stamped by the server) says whose row it is and
|
|
71
|
-
`host` says which machine shipped it. Search the whole house by default; add
|
|
72
|
-
`AND user_id = '<name>'` only when the user asks for one person's sessions.
|
|
73
|
-
|
|
74
|
-
**Keep the SQL heredoc quoted (`<<'SQL'`).** This skill once told the agent to substitute
|
|
75
|
-
a shell variable into the table names, which cannot expand inside a quoted heredoc — so
|
|
76
|
-
the agent unquotes it, and then the SEARCH TERMS expand too. Both instances driving this
|
|
77
|
-
skill did exactly that, unprompted, on the first attempt. Measured consequences, from
|
|
78
|
-
`system.query_log`:
|
|
79
|
-
|
|
80
|
-
- a term containing `$home` became the empty string, so `LIKE '%%'` matched **every row**
|
|
81
|
-
and reported hits with exit 0 — silently wrong, not an error;
|
|
82
|
-
- a term containing a backtick **executed the command inside it**.
|
|
83
|
-
|
|
84
|
-
A user's search term is arbitrary text. With the heredoc quoted, neither can happen.
|
|
85
|
-
|
|
86
|
-
**There is no `sessions_v` object.** The session rollup is a saved query over those same
|
|
87
|
-
rooms — `memhouse sessions-query` prints it for whoever you are connected as, ready to
|
|
88
|
-
paste into a `FROM (...) AS c` position.
|
|
89
|
-
|
|
90
|
-
**The rollup is self-contained.** As of 0.4.5 the printed text carries its own `FINAL` on
|
|
91
|
-
both rooms and a trailing `SETTINGS join_use_nulls = 1`, so it is correct wherever you
|
|
92
|
-
paste it. Both matter: without `FINAL` every message is counted once per undeleted
|
|
93
|
-
ReplacingMergeTree version (2x right after a ship, 3x a few ships later — measured, and it
|
|
94
|
-
grows); without `join_use_nulls` a session with no messages reports `total_msgs = 1`
|
|
95
|
-
rather than 0.
|
|
96
|
-
|
|
97
|
-
**If the `memhouse` binary is not on PATH**, you cannot print the rollup — the skills are
|
|
98
|
-
installable on their own. Query the rooms directly instead: `sessions` for metadata
|
|
99
|
-
and `messages` for counts, each read `FINAL`, joined on `session_id` and `user_id`.
|
|
100
|
-
Prefer the binary when it is there; a rollup you assemble by hand and one printed by a
|
|
101
|
-
DIFFERENT memhouse version are the two ways this goes quietly wrong.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## Default listing
|
|
105
|
-
|
|
106
|
-
```sql
|
|
107
|
-
SELECT session_id, name, source, host, project,
|
|
108
|
-
started, ended, total_msgs, user_msgs, assistant_msgs,
|
|
109
|
-
output_tokens,
|
|
110
|
-
substring(first_prompt, 1, 80) AS first_prompt
|
|
111
|
-
FROM (the rollup from `memhouse sessions-query`)
|
|
112
|
-
ORDER BY ended DESC
|
|
113
|
-
LIMIT 20
|
|
114
|
-
FORMAT PrettyCompact
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
- Default `LIMIT 20`; `[N]` overrides.
|
|
118
|
-
- `today` / `week` / `month` → `WHERE ended > now() - INTERVAL 1 DAY`
|
|
119
|
-
(`7 DAY` / `30 DAY`).
|
|
120
|
-
- `project <name>` → `WHERE project ILIKE '%<name>%'`.
|
|
121
|
-
- `from <editor>` → `WHERE source = '<id>'` (`claude-code`, `codex`, `cursor`,
|
|
122
|
-
`vscode`, `zed`, `opencode`, `gemini-cli`, `windsurf`, …).
|
|
123
|
-
- Unnamed sessions (`name = ''`): fall back to `first_prompt` as the label.
|
|
124
|
-
|
|
125
|
-
## More cuts
|
|
126
|
-
|
|
127
|
-
Per-editor rollup ("where has my work been happening?"):
|
|
128
|
-
|
|
129
|
-
```sql
|
|
130
|
-
SELECT source, count() AS sessions, sum(total_msgs) AS msgs,
|
|
131
|
-
sum(output_tokens) AS out_tokens, max(ended) AS last_activity
|
|
132
|
-
FROM (the rollup from `memhouse sessions-query`)
|
|
133
|
-
GROUP BY source
|
|
134
|
-
ORDER BY sessions DESC
|
|
135
|
-
FORMAT PrettyCompact
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Busiest projects this month:
|
|
139
|
-
|
|
140
|
-
```sql
|
|
141
|
-
SELECT project, count() AS sessions, sum(assistant_msgs) AS assistant_msgs
|
|
142
|
-
FROM (the rollup from `memhouse sessions-query`)
|
|
143
|
-
WHERE ended > now() - INTERVAL 30 DAY AND project != ''
|
|
144
|
-
GROUP BY project
|
|
145
|
-
ORDER BY sessions DESC
|
|
146
|
-
LIMIT 15
|
|
147
|
-
FORMAT PrettyCompact
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Examples
|
|
151
|
-
|
|
152
|
-
| User says | What you do |
|
|
153
|
-
|---|---|
|
|
154
|
-
| "what was I working on yesterday?" | `WHERE ended > now() - INTERVAL 1 DAY` |
|
|
155
|
-
| "show my last 10 sessions in agentlytics" | `LIMIT 10 … WHERE project ILIKE '%agentlytics%'` |
|
|
156
|
-
| "list my cursor sessions this month" | `WHERE source = 'cursor' AND ended > now() - INTERVAL 30 DAY` |
|
|
157
|
-
| "which machine did I do the schema work on?" | search via /mem:search, or filter `host` here |
|
|
158
|
-
|
|
159
|
-
## Output
|
|
160
|
-
|
|
161
|
-
A compact table: when, name (or first prompt), source, host, project, msgs,
|
|
162
|
-
tokens. Offer to pull a full transcript next
|
|
163
|
-
(`SELECT role, text FROM messages WHERE session_id = '…' ORDER BY seq`) or to
|
|
164
|
-
run /mem:search for a specific quote.
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: share
|
|
3
|
-
description: Share your conversation memory with another user on the same ClickHouse — grant them read-only access to your house, list who can currently read it, or revoke a share. Use when the user says "share my memory with X", "let X see my sessions", "who can read my memory", "stop sharing with X". Read-only by construction — a share can never let anyone write to or delete your memory.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
argument-hint: "[<user> | revoke <user> | (nothing: list current shares)]"
|
|
6
|
-
allowed-tools: Bash
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# /mem:share — a read-only window into your memory
|
|
10
|
-
|
|
11
|
-
Three forms, decided by the arguments:
|
|
12
|
-
|
|
13
|
-
- `/mem:share yigit` — let `yigit` read this house
|
|
14
|
-
- `/mem:share revoke yigit` — close that window
|
|
15
|
-
- `/mem:share` — who can read this house right now
|
|
16
|
-
|
|
17
|
-
Sharing is a ClickHouse GRANT and nothing else — memhouse adds no layer on top, so what
|
|
18
|
-
you grant is exactly what they get: `SELECT`, never write, never delete. Your own grant
|
|
19
|
-
carries `WITH GRANT OPTION` on SELECT only (installs from 0.10.0 on), so you can open
|
|
20
|
-
this window yourself and can hand on nothing more.
|
|
21
|
-
|
|
22
|
-
## Connection
|
|
23
|
-
|
|
24
|
-
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no `env | grep
|
|
25
|
-
MEMHOUSE`, no `set -x` around these commands.
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
29
|
-
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
30
|
-
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
31
|
-
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
32
|
-
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
33
|
-
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
34
|
-
echo "no memhouse house configured — nothing in $MH_ENV and no MEMHOUSE_URL/MEMHOUSE_USER set." >&2
|
|
35
|
-
echo "Run: memhouse install" >&2
|
|
36
|
-
exit 1
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# NO readonly=1 on this skill's grant/revoke calls — GRANT is refused under readonly.
|
|
40
|
-
# The list query below still pins it.
|
|
41
|
-
curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
42
|
-
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-$MEMHOUSE_USER}" <<'SQL'
|
|
43
|
-
<the statement>
|
|
44
|
-
SQL
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Grant — `/mem:share <user>`
|
|
48
|
-
|
|
49
|
-
The user name goes into SQL unquoted: **refuse anything not matching
|
|
50
|
-
`[A-Za-z][A-Za-z0-9_]*`** rather than quoting it — a name you cannot type bare is a name
|
|
51
|
-
that will be gotten wrong everywhere else too.
|
|
52
|
-
|
|
53
|
-
**Validate `<user>` FIRST, once, and use only the validated token — in the GRANT, the
|
|
54
|
-
REVOKE, and the house_meta record alike.** The record INSERT is a quoted literal;
|
|
55
|
-
anything that has not passed the name check must never reach it.
|
|
56
|
-
|
|
57
|
-
**`<db>` below is the LIVE value of `$MEMHOUSE_DB` — print it with the connection
|
|
58
|
-
(`echo "house: $MEMHOUSE_DB"`) and use THAT in every statement and every piece of
|
|
59
|
-
advice.** Driven live, a model filled `<db>` in the admin advice with the literal
|
|
60
|
-
fallback `mem` while the real house was named differently — advice the admin would have
|
|
61
|
-
run against the wrong database.
|
|
62
|
-
|
|
63
|
-
```sql
|
|
64
|
-
GRANT SELECT ON <db>.* TO <user>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
(`<db>` is `$MEMHOUSE_DB`.) Then RECORD it in the house's own memory — a member cannot
|
|
68
|
-
read `system.grants` (measured: `Not enough privileges … SELECT ON system.grants`), so
|
|
69
|
-
this record is what the list form reads back:
|
|
70
|
-
|
|
71
|
-
```sql
|
|
72
|
-
INSERT INTO house_meta (key, value) VALUES ('share:<user>', 'granted <today, YYYY-MM-DD>')
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Verify the grant took by USING it if you can, or simply report the server accepted it —
|
|
76
|
-
do not call `SHOW GRANTS FOR <user>` (it needs privileges a member does not hold).
|
|
77
|
-
|
|
78
|
-
Two failure shapes, both to report exactly:
|
|
79
|
-
|
|
80
|
-
- `There is no user ... in user directories` — the person has no ClickHouse user on this
|
|
81
|
-
server yet. Joining comes first: they run `memhouse install` against this server (or
|
|
82
|
-
the admin runs `memhouse install --print-sql --member <user> --db <their-house>`).
|
|
83
|
-
A share cannot create a user, by design.
|
|
84
|
-
- `Not enough privileges` — either this house predates 0.10.0 (your own grant carries
|
|
85
|
-
no grant option), or **the configured house is not yours to share** (a team house, or
|
|
86
|
-
someone else's). Only suggest the admin remedy when the house is the user's OWN — the
|
|
87
|
-
grant-option statement hands them standing power to share it, which is the owner's
|
|
88
|
-
decision, not a fix. For their own pre-0.10 house, print for the ADMIN:
|
|
89
|
-
`GRANT SELECT ON <db>.* TO <user>` — and to make future shares self-serve:
|
|
90
|
-
`GRANT SELECT ON <db>.* TO <your-user> WITH GRANT OPTION`.
|
|
91
|
-
For a house that is not theirs, say so and stop: the owner shares it, not you.
|
|
92
|
-
|
|
93
|
-
Tell the user how their friend actually reads it: qualified names —
|
|
94
|
-
`SELECT … FROM <db>.messages …` — from /mem:ask, /mem:search or /mem:sql on the friend's
|
|
95
|
-
side, with the same epoch filter those skills already carry.
|
|
96
|
-
|
|
97
|
-
## Revoke — `/mem:share revoke <user>`
|
|
98
|
-
|
|
99
|
-
```sql
|
|
100
|
-
REVOKE SELECT ON <db>.* FROM <user>
|
|
101
|
-
```
|
|
102
|
-
```sql
|
|
103
|
-
INSERT INTO house_meta (key, value) VALUES ('share:<user>', 'revoked <today, YYYY-MM-DD>')
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Revoking a share that never existed is a no-op, not an error — say which it was (the
|
|
107
|
-
`share:<user>` record answers that).
|
|
108
|
-
|
|
109
|
-
## List — `/mem:share` (no arguments)
|
|
110
|
-
|
|
111
|
-
From the house's own record (append `&readonly=1` to the URL for this one):
|
|
112
|
-
|
|
113
|
-
```sql
|
|
114
|
-
SELECT substring(key, 7) AS user, value AS state,
|
|
115
|
-
formatDateTime(updated_at, '%Y-%m-%d %H:%i') AS at
|
|
116
|
-
FROM house_meta FINAL WHERE key LIKE 'share:%' ORDER BY key
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Latest-wins per user: `granted …` rows are live shares, `revoked …` rows are closed
|
|
120
|
-
ones. Be honest about the record's edge: it shows shares made THROUGH THIS SKILL — a
|
|
121
|
-
grant the admin issued directly will not appear. The authoritative list is the admin's
|
|
122
|
-
one query: `SELECT user_name, access_type FROM system.grants WHERE database = '<db>'`
|
|
123
|
-
(members cannot run it; `system.grants` needs privileges a member does not hold).
|
|
124
|
-
|
|
125
|
-
## What this never does
|
|
126
|
-
|
|
127
|
-
No `GRANT ALL`, no write grants, no `WITH GRANT OPTION` onward, no user creation, no
|
|
128
|
-
grants on any database but your own. If the user asks for more than read access, that is
|
|
129
|
-
the shared-house model (both members in ONE database) — a decision, not a grant; point
|
|
130
|
-
them at `memhouse install --db <shared>` and stop.
|