memhouse 0.12.6 → 0.13.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 CHANGED
@@ -76,12 +76,22 @@ memhouse update [--check] upgrade, restart daemons, check the s
76
76
  memhouse start | stop dashboard + shipper as daemons
77
77
  memhouse service install | uninstall survive a reboot
78
78
  memhouse deploy --local | --down stand up (or remove) a local house
79
- memhouse plugins install claude /mem:search, :sessions, :sql —
80
- into every Claude Code config dir found
79
+ memhouse invite <name> mint a member + house, hand them one env file
80
+ memhouse passwd rotate this member's password
81
+ memhouse migrate | migrate-rooms [--dry-run] [--yes]
82
+ run whatever this house still needs
83
+ memhouse relocate --to <url> copy this house to a new ClickHouse, then repoint
84
+ memhouse nightly [--out DIR] build an installable tarball from this checkout
85
+ memhouse plugins install claude 9 skills — ask, hello, invite, search,
86
+ sessions, share, sql, status, users — into
87
+ every Claude Code config dir found
81
88
  memhouse prompt memory snippet for an agent's system prompt
82
89
  memhouse prompt --install an install prompt, rendered for this machine
83
90
  ```
84
91
 
92
+ Any command's own usage: `memhouse <command> --help` (or `-h`) — always prints usage,
93
+ never runs the command.
94
+
85
95
  Every command works both ways: interactive for you, `--yes` / flags / `--json` for
86
96
  an agent — so an agent can install its own memory unattended.
87
97
 
package/bin/memhouse.js CHANGED
@@ -326,7 +326,8 @@ Setup onboard interactive wizard: discover → configure →
326
326
  invite <name> mint a member + house on the server and write the env
327
327
  file their install needs (--url --admin-user
328
328
  --admin-password [--db NAME] [--out FILE]); local
329
- machine untouched
329
+ machine untouched. Refuses a house that already holds
330
+ someone's messages — --adopt if sharing it is intended
330
331
  passwd rotate this member's password + rewrite the env file
331
332
  (admin-assisted: --admin-user --admin-password)
332
333
  --print-sql print the SQL, run it yourself
@@ -667,15 +668,13 @@ CREATE USER IF NOT EXISTS ${member} IDENTIFIED BY '${password.replace(/'/g, "\\'
667
668
 
668
669
  ${rooms.trim()}
669
670
 
670
- -- The whole database. ALL on your own house reaches nothing outside it — the database is
671
- -- the boundary — and it is what lets the shipper create and evolve its own tables.
672
- GRANT ALL ON ${db}.* TO ${member};
673
-
674
- -- SELECT again, WITH GRANT OPTION — sharing, made self-serve and read-only by
675
- -- construction. This is what lets the member run \`GRANT SELECT ON ${db}.* TO <friend>\`
676
- -- themselves (/mem:share) without an operator, while the grant-option stops at SELECT:
677
- -- they can open a read-only window into their own memory and can hand on nothing more.
678
- GRANT SELECT ON ${db}.* TO ${member} WITH GRANT OPTION;
671
+ -- The whole database, WITH GRANT OPTION. ALL on your own house reaches nothing outside it
672
+ -- — the database is the boundary — and it lets the shipper create and evolve its own
673
+ -- tables. The grant option makes the member the real owner: they can hand on any of their
674
+ -- own data (\`/mem:share\` still opens only a read-only SELECT window, but the owner is not
675
+ -- boxed into read-only sharing of their own house). Scoped to this db, and no CREATE USER
676
+ -- rides with it — a member still cannot mint accounts or reach another house.
677
+ GRANT ALL ON ${db}.* TO ${member} WITH GRANT OPTION;
679
678
 
680
679
  -- Self-scoped ALTER USER: the member may change THEIR OWN password and no one else's.
681
680
  -- \`ON ${member}\` names exactly this user, so \`memhouse passwd\` needs no admin, and an
@@ -692,6 +691,12 @@ GRANT ALTER USER ON ${member} TO ${member};
692
691
  -- It grants no read of anyone's rows; reading a housemate's memory still needs /mem:share.
693
692
  GRANT SHOW USERS ON *.* TO ${member};
694
693
 
694
+ -- \`memhouse relocate\` runs FROM the destination, pulling the source over remoteSecure() —
695
+ -- a table function ClickHouse gates behind its own access type, separate from GRANT ALL on
696
+ -- a database. Without this, relocate fails at the native-reachability probe with
697
+ -- ACCESS_DENIED (source untouched, nothing lost — just unusable until granted).
698
+ GRANT REMOTE ON *.* TO ${member};
699
+
695
700
  -- Pin async_insert on the user (ADD SETTING merges; a bare SETTINGS clause would replace
696
701
  -- the user's whole list). The pin keeps the user_id stamp honest: a MATERIALIZED
697
702
  -- currentUser() is computed during the INSERT, and an async flush stores it as the empty
@@ -788,11 +793,12 @@ async function adminBootstrap(cfg, admin) {
788
793
  // reaches nothing outside the database, and it is what lets the member's own shipper
789
794
  // create and evolve the rooms (--ensure-schema below).
790
795
  try {
791
- await q(`GRANT ALL ON ${cfg.db}.* TO ${admin.member}`, { database: '' });
792
- // SELECT again, WITH GRANT OPTION: what /mem:share rides on. The member can open a
793
- // read-only window into their OWN house for a housemate-to-be — and can hand on
794
- // nothing more, because the grant option stops at SELECT.
795
- await q(`GRANT SELECT ON ${cfg.db}.* TO ${admin.member} WITH GRANT OPTION`, { database: '' });
796
+ // ALL, WITH GRANT OPTION: the database is theirs, so they may do anything with their
797
+ // own data AND hand any of it on. /mem:share still opens only a read-only window (it
798
+ // grants SELECT), but the owner is not boxed into read-only sharing of their own house.
799
+ // Scoped to their db: the grant option reaches nothing outside it, and no CREATE USER
800
+ // comes with it, so a member still cannot mint accounts or touch another house.
801
+ await q(`GRANT ALL ON ${cfg.db}.* TO ${admin.member} WITH GRANT OPTION`, { database: '' });
796
802
  // Self-scoped ALTER USER — the member owns their own password (memhouse passwd needs
797
803
  // no admin; an invitee can rotate the password the inviter set). Non-escalating: the
798
804
  // grant names this one user, so it reaches no other account. Best-effort: a house on
@@ -806,8 +812,16 @@ async function adminBootstrap(cfg, admin) {
806
812
  // Best-effort like ALTER USER above — an admin without access-management just skips it.
807
813
  try { await q(`GRANT SHOW USERS ON *.* TO ${admin.member}`, { database: '' }); }
808
814
  catch { /* no access-management: /mem:users section 4 stays admin-only for this member */ }
815
+ // REMOTE: `memhouse relocate` runs FROM the destination, pulling the source over
816
+ // remoteSecure() — a table function ClickHouse gates behind its own access type,
817
+ // separate from any GRANT ALL on a database. Without it every relocate a member runs
818
+ // fails at the native-reachability probe with ACCESS_DENIED, source untouched, no
819
+ // data lost — just relocate unusable until an admin grants this by hand. Best-effort
820
+ // like the two grants above.
821
+ try { await q(`GRANT REMOTE ON *.* TO ${admin.member}`, { database: '' }); }
822
+ catch { /* no access-management: relocate stays admin-assisted for this member */ }
809
823
  await q(`ALTER USER ${admin.member} ADD SETTING ${MEMBER_PIN}`, { database: '' });
810
- console.log(ok(`granted the house: ALL ON ${cfg.db}.* to '${admin.member}', SHOW USERS, async_insert pinned`));
824
+ console.log(ok(`granted the house: ALL ON ${cfg.db}.* to '${admin.member}', SHOW USERS, REMOTE, async_insert pinned`));
811
825
  } catch (e) {
812
826
  console.log(bad(`could not grant the house to '${admin.member}': ${e.message}`));
813
827
  if (createdUser) {
@@ -2899,7 +2913,7 @@ async function cmdNightly() {
2899
2913
  */
2900
2914
  async function cmdInvite() {
2901
2915
  const name = positional[0];
2902
- if (!name) { console.log('usage: memhouse invite <name> --url <house-url> --admin-user … --admin-password … [--db <house>] [--out <file>]'); return 2; }
2916
+ if (!name) { console.log('usage: memhouse invite <name> --url <house-url> --admin-user … --admin-password … [--db <house>] [--out <file>] [--adopt]'); return 2; }
2903
2917
  try { require(path.join(REPO_ROOT, 'memhouse', 'house', 'house')).assertUsableName(name, 'member'); }
2904
2918
  catch (e) { console.log(bad(e.message)); return 1; }
2905
2919
  const cfg = resolveConfig();
@@ -2952,6 +2966,42 @@ async function cmdInvite() {
2952
2966
  // EXPLICIT --admin-* (where the admin typed the target themselves) provisions at --url.
2953
2967
  // Either way the invite FILE carries --url, the invitee's path.
2954
2968
  const provisionUrl = flags['admin-user'] ? url : cfg.url;
2969
+
2970
+ // Is there already a house here, with somebody's memory in it? The house is created
2971
+ // with CREATE DATABASE IF NOT EXISTS, so inviting a name whose database already exists
2972
+ // ADOPTS it — same output as a fresh one, and the invitee lands on top of rows that are
2973
+ // not theirs. (The user half is safe: adminBootstrap refuses an existing ClickHouse
2974
+ // user, so no sitting member's password is ever rotated out from under them.)
2975
+ //
2976
+ // Found the hard way: an invite meant for one person was sent to another, who shipped
2977
+ // 3,733 messages under it. Re-inviting the intended person reported success and handed
2978
+ // them the first person's memory, with nothing on any surface saying so.
2979
+ //
2980
+ // Read-only, best-effort: an admin that cannot count rows should not lose the ability
2981
+ // to invite, and a house that does not exist yet is the ordinary case.
2982
+ const adminCfg = { ...cfg, url: provisionUrl, user: adminUser, password: adminPass || '', db, stated: true };
2983
+ let occupied = null;
2984
+ try {
2985
+ const exists = await chRows(adminCfg, `SELECT count() AS n FROM system.tables WHERE database = '${db.replace(/'/g, "\\'")}' AND name = 'messages'`, { database: '' });
2986
+ if (Number(exists[0] && exists[0].n) > 0) {
2987
+ const r = await chRows(adminCfg, 'SELECT count() AS msgs, uniqExact(user_id) AS writers FROM messages', { database: db });
2988
+ const msgs = Number(r[0] && r[0].msgs) || 0;
2989
+ if (msgs > 0) occupied = { msgs, writers: Number(r[0].writers) || 0 };
2990
+ }
2991
+ } catch { /* cannot tell — provisioning below will surface any real access problem */ }
2992
+ if (occupied && flags.adopt !== true) {
2993
+ console.log(bad(`house '${db}' already exists and holds ${occupied.msgs} messages from ${occupied.writers} writer(s) — NOT inviting.`));
2994
+ console.log(` Inviting '${name}' here would hand them somebody else's memory, and their`);
2995
+ console.log(' first ship would land on top of it. Nothing has been changed.');
2996
+ console.log(' Pick a different handle: memhouse invite <other-name> --url …');
2997
+ console.log(` Or a different house: memhouse invite ${name} --url … --db <house>`);
2998
+ console.log(` If sharing this house IS the intent, say so: --adopt`);
2999
+ return 1;
3000
+ }
3001
+ if (occupied) {
3002
+ console.log(warn(`adopting existing house '${db}' — ${occupied.msgs} messages already here (--adopt)`));
3003
+ }
3004
+
2955
3005
  const built = await adminBootstrap({ ...cfg, url: provisionUrl, db, stated: true }, {
2956
3006
  user: adminUser, password: adminPass || '', member: name, quiet: true,
2957
3007
  });
@@ -3203,6 +3253,13 @@ async function cmdUninstall() {
3203
3253
  if (flags.version === true || process.argv.slice(2).some((a) => a === '-v' || a === '-V')) {
3204
3254
  console.log(PKG.version); return;
3205
3255
  }
3256
+ // `memhouse <cmd> --help` (or `-h`) must never reach a command's own logic — passwd,
3257
+ // invite, uninstall and friends act on first call with no separate confirm step, so a
3258
+ // --help that fell through to the default branch would DO the thing instead of
3259
+ // describing it. One check ahead of the switch, for every command, is the whole fix.
3260
+ if (cmd && (flags.help === true || process.argv.slice(2).some((a) => a === '-h' || a === '--help'))) {
3261
+ console.log(HELP); return;
3262
+ }
3206
3263
  const cfg = resolveConfig();
3207
3264
 
3208
3265
  switch (cmd) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mem",
3
- "description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: ask, search, browse, analyze, check, share, and invite across all 17 supported editors and machines. Skills install as /mem:hello, /mem:ask, /mem:search, /mem:sessions, /mem:share, /mem:invite, /mem:sql, /mem:status, and /mem:users.",
4
- "version": "0.12.6",
3
+ "description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: ask, search, browse, analyze, check, share, and invite across all 17 supported editors and machines, plus administration of the ClickHouse behind it. Skills install as /mem:hello, /mem:ask, /mem:search, /mem:sessions, /mem:share, /mem:invite, /mem:sql, /mem:status, /mem:users, and /mem:admin.",
4
+ "version": "0.13.0",
5
5
  "author": {
6
6
  "name": "Ramazan Polat"
7
7
  },
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: admin
3
+ description: Operate the memhouse ClickHouse as its administrator — list and inspect every user and house, provision or remove members, grant and revoke access, and read server-wide health (disk, parts, mutations, running queries). Use when the user asks to "do X as admin", "manage users", "drop a house", "see disk usage", "who is on this server", or when a member-scoped skill refused for want of privileges. Requires an admin credential; says so plainly when there is not one.
4
+ user-invocable: true
5
+ argument-hint: "<what you want done>"
6
+ allowed-tools: Bash
7
+ ---
8
+
9
+ # /mem:admin — operate the house as its administrator
10
+
11
+ A member owns one database. An **administrator** owns the ClickHouse: every house, every
12
+ account, every grant. This skill is the second one, and it is the only skill allowed to
13
+ act outside the caller's own house.
14
+
15
+ Everything here runs through the same rule as `/mem:invite`: **an admin password must
16
+ never enter this conversation.** memhouse ships this transcript into the house you are
17
+ administering, so a password pasted in chat is a password published to the archive.
18
+ Resolve it from the environment or a file, never from a prompt, and never echo it.
19
+
20
+ ## 1. Find an admin credential — never ask for one in chat
21
+
22
+ Try these in order and stop at the first that works. **Never print any of these files or
23
+ the variables in them** — no `cat`, no `env | grep`, no `set -x`.
24
+
25
+ ```bash
26
+ MH_HOME="${MEMHOUSE_HOME:-$HOME/.memhouse}"
27
+ # a) an explicit admin credential in the environment
28
+ ADM_U=${MEMHOUSE_ADMIN_USER-}; ADM_P=${MEMHOUSE_ADMIN_PASSWORD-}
29
+ # b) an admin env file kept beside the member config
30
+ if [ -z "$ADM_U" ] && [ -f "$MH_HOME/admin.env" ]; then
31
+ set -a; . "$MH_HOME/admin.env"; set +a
32
+ ADM_U=${MEMHOUSE_ADMIN_USER:-${MEMHOUSE_USER-}}; ADM_P=${MEMHOUSE_ADMIN_PASSWORD:-${MEMHOUSE_PASSWORD-}}
33
+ fi
34
+ # c) the ordinary member credential — on a house the pilot stood up themselves
35
+ # (memhouse deploy --local) the member IS the superuser
36
+ set -a; [ -f "$MH_HOME/env" ] && . "$MH_HOME/env"; set +a
37
+ : "${ADM_U:=${MEMHOUSE_USER-}}"; : "${ADM_P:=${MEMHOUSE_PASSWORD-}}"
38
+ ```
39
+
40
+ Then **prove** it is actually an admin rather than assuming — a member can often read
41
+ `system.users` while holding nothing else:
42
+
43
+ ```bash
44
+ curl -sS --fail-with-body --user "$ADM_U:$ADM_P" \
45
+ --data-binary "SHOW GRANTS FOR $ADM_U FORMAT TSV" "$MEMHOUSE_URL/"
46
+ ```
47
+
48
+ Admin means the grants carry `ACCESS MANAGEMENT`, or `CREATE USER`/`CREATE DATABASE` at
49
+ `*.*` — not merely `SHOW USERS`, which every member has. If none of a/b/c qualifies,
50
+ **stop and say so**:
51
+
52
+ > This needs administrator access to the ClickHouse at `<url>`, and the credential
53
+ > configured here is the member account `<user>` — it owns its own house and nothing
54
+ > else. To use `/mem:admin`, put an admin credential in `~/.memhouse/admin.env`
55
+ > (`MEMHOUSE_ADMIN_USER=` / `MEMHOUSE_ADMIN_PASSWORD=`, mode 600) or export
56
+ > `MEMHOUSE_ADMIN_USER` / `MEMHOUSE_ADMIN_PASSWORD`, then ask again. Whoever operates
57
+ > the server has it; on a house from `memhouse deploy --local` it is the account already
58
+ > in `~/.memhouse/env`.
59
+
60
+ Do not offer to carry on with reduced powers, do not suggest the user paste a password
61
+ into the chat, and do not improvise around the refusal. Say which of a/b/c you tried.
62
+
63
+ ## 2. Run the request
64
+
65
+ Use the resolved credential for every query. `--fail-with-body` so ClickHouse's own error
66
+ is visible; add `&readonly=1` on anything that only reads.
67
+
68
+ ```bash
69
+ adm() { curl -sS --fail-with-body --user "$ADM_U:$ADM_P" --data-binary "$1" "$MEMHOUSE_URL/${2-}"; }
70
+ adm "SELECT … FORMAT PrettyCompact" "?readonly=1"
71
+ ```
72
+
73
+ Reads worth knowing — reach for these before inventing SQL:
74
+
75
+ | Question | Query |
76
+ |---|---|
77
+ | who exists | `SELECT name FROM system.users ORDER BY name` |
78
+ | what can someone do | `SHOW GRANTS FOR <user>` |
79
+ | every house and its size | `SELECT database, formatReadableSize(sum(bytes_on_disk)) AS disk, sum(rows) AS rows FROM system.parts WHERE active GROUP BY database ORDER BY sum(bytes_on_disk) DESC` |
80
+ | a house's rooms | `SELECT table, formatReadableSize(sum(bytes_on_disk)) AS disk, sum(rows) AS rows FROM system.parts WHERE active AND database = '<db>' GROUP BY table` |
81
+ | who writes where | `SELECT user_id, host, count() FROM <db>.messages GROUP BY user_id, host` |
82
+ | running queries | `SELECT query_id, user, elapsed, formatReadableSize(memory_usage) AS mem, substring(query,1,120) AS q FROM system.processes` |
83
+ | unfinished mutations | `SELECT database, table, mutation_id, command, parts_to_do, latest_fail_reason FROM system.mutations WHERE NOT is_done` |
84
+ | merge backlog | `SELECT database, table, elapsed, progress FROM system.merges` |
85
+
86
+ Writes follow the shapes memhouse itself uses, so an admin-made member is identical to an
87
+ invited one — a member gets `GRANT ALL ON <db>.* … WITH GRANT OPTION`, plus `SHOW USERS`,
88
+ self-scoped `ALTER USER`, `REMOTE`, and the async-insert pin. **Prefer the CLI over raw
89
+ SQL when one exists**: `memhouse invite <name> --url …` provisions a member correctly
90
+ (and refuses a house that already holds someone's messages); this skill is for the things
91
+ it does not cover.
92
+
93
+ ## 3. Destructive work — look first, then confirm
94
+
95
+ `DROP DATABASE`, `DROP USER`, `REVOKE`, `ALTER … DELETE`, `TRUNCATE`, `KILL QUERY` and
96
+ password rotation all change something a person depends on. There are no backups and no
97
+ undo: a dropped house is gone, and the transcripts behind it may have aged out of the
98
+ editors that produced them.
99
+
100
+ Before any of them:
101
+
102
+ 1. **Look at the target and report what is actually there** — row counts per room, who
103
+ wrote them, the date span, which machines. A house you expected to be empty may not
104
+ be.
105
+ 2. **Name the collateral.** Dropping a user stops that account's shipper on every machine
106
+ it runs on. Revoking a grant closes a share the other person may be mid-query on.
107
+ Rotating a password breaks every machine shipping under it.
108
+ 3. **Get an explicit yes for that specific object**, quoting the counts you just read.
109
+ A general "yes do admin things" is not consent to drop a populated house.
110
+ 4. **Verify afterwards** and say what is now true.
111
+
112
+ If the user has already told you exactly what to destroy and why, and the counts match
113
+ their description, proceed — do not re-litigate a decision they have made. Report the
114
+ outcome plainly, including the row counts destroyed.
115
+
116
+ ## Never
117
+
118
+ Never accept an admin password typed into the conversation, and never print one — not in
119
+ a command, not in an echo, not in an error. If a command needs it, expand a shell
120
+ variable (`--admin-password "$ADM_P"`), so the value never appears in the transcript.
121
+
122
+ Never widen a member's grants beyond what `memhouse invite` issues without the user
123
+ asking for that specific privilege and being told what it allows. Never grant
124
+ `ACCESS MANAGEMENT`, `CREATE USER`, or anything at `*.*` casually — those make a second
125
+ administrator.
126
+
127
+ Never touch a house that is not the subject of the request. Never run a destructive
128
+ statement whose predicate you have not first run as a `SELECT`.
@@ -30,9 +30,10 @@ substituted into a command — that burns the credential into the transcript on
30
30
  Only ask about a **shared house** if the user brings one up; default is the invitee's
31
31
  own house (omit --db; never pass `--db default` for "the default house").
32
32
 
33
- 3. TRY IT BARE FIRST — run this via the Bash tool. `memhouse invite` uses the caller's
34
- OWN configured credential when it can manage users, which is the common case for
35
- whoever set the house up, so no admin is needed:
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:
36
37
 
37
38
  ```
38
39
  memhouse invite <name> --url <url> [--db <shared-house>]
@@ -50,16 +51,52 @@ substituted into a command — that burns the credential into the transcript on
50
51
  memhouse invite <name> --url <url> [--db <shared-house>] --admin-user <admin> --admin-password 'PASTE_IT_HERE'
51
52
  ```
52
53
 
53
- 4. When it succeeds it prints `invite written: invite-<name>.env`. Relay to the user:
54
- - hand `invite-<name>.env` to the invitee over a trusted channel (croc, a password
55
- manager — not chat); the file IS a password
56
- - the invitee runs `memhouse install --env invite-<name>.env` — it OFFERS to change
57
- the password to one only they know (they were granted rotation on their own account),
58
- then deletes the file; so the password you set stops working once they install
59
- - once installed, sharing works: `/mem:share <name>` on either side
54
+ **If it refuses with "house `<db>` already exists and holds N messages", STOP.** That
55
+ house is somebody's memory, and inviting into it would hand the invitee someone else's
56
+ sessions. Do not pass `--adopt` on your own initiative — report the row count and the
57
+ writer count to the user and let them choose: a different handle, a different `--db`,
58
+ or `--adopt` if sharing that house is genuinely what they want.
59
+
60
+ 4. **Put the file where the user can attach it.** The CLI writes `invite-<name>.env`
61
+ into the CURRENT directory, and the user has to send it by hand — so run the command
62
+ from their downloads directory (on macOS and most Linux desktops `~/Downloads`), or
63
+ move it there afterwards. Never leave it in a git repo: it is a password, and
64
+ `invite-*.env` is only gitignored in this repo, not in whatever checkout the user
65
+ happened to be standing in.
66
+
67
+ Confirm it is there and still mode `600`, and say the full path. **Never print the
68
+ file's contents** — the password inside would land in a transcript memhouse ships.
69
+
70
+ 5. **Say whether the house was fresh.** Run
71
+ `memhouse stats` or query `SELECT count() FROM <db>.messages` and tell the user
72
+ plainly: a brand-new house reads 0. This is a second line of defence behind step 3's
73
+ refusal, and it is how the user learns the invitee is starting clean.
74
+
75
+ 6. **Write the message the user will send.** They are about to hand over a file plus an
76
+ explanation, so compose it for them rather than making them write it. Put it in a
77
+ fenced block they can copy whole, and cover:
78
+ - what memhouse is, in a sentence or two — the invitee may never have heard of it
79
+ - that the attached `invite-<name>.env` IS a password: don't forward it, don't paste
80
+ it into chat
81
+ - `npm install -g memhouse` (Node 24+, no flags, nothing compiles), then
82
+ `memhouse install --env invite-<name>.env`
83
+ - that install OFFERS to change the password to one only they know, then deletes the
84
+ file — so they should say yes, because the inviter knows the current one
85
+ - `memhouse onboard` to start shipping, `memhouse status` to check it worked
86
+ - that their house is theirs alone; the inviter cannot read it unless they run
87
+ `/mem:share <inviter>`
88
+
89
+ If the invitee is REPLACING an existing setup (they were shipping under another
90
+ credential), add `memhouse ship --full` after the install — a plain incremental pass
91
+ skips sessions the new house has no record of, so their history would not follow them.
92
+
93
+ Then: once installed, sharing works both ways — `/mem:share <name>`.
60
94
 
61
95
  ## Never
62
96
 
63
97
  Never create a ClickHouse user by running raw SQL with an admin password you were given
64
98
  in chat. Never invent an admin credential. Never suggest granting beyond what `memhouse
65
99
  invite` does. If the binary is absent, stop and say so — do not improvise the SQL.
100
+
101
+ Never print the invite file's contents, and never pass `--adopt` unless the user asked
102
+ for it after being told what is already in that house.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memhouse",
3
- "version": "0.12.6",
4
- "description": "memhouse — agent conversation memory across 17 editors: parse-on-client shippers, a typed ClickHouse store with one set of rooms per member, kernel-agency install, and the agentlytics dashboard",
3
+ "version": "0.13.0",
4
+ "description": "memhouse \u2014 agent conversation memory across 17 editors: parse-on-client shippers, a typed ClickHouse store with one set of rooms per member, kernel-agency install, and the agentlytics dashboard",
5
5
  "main": "bin/memhouse.js",
6
6
  "bin": {
7
7
  "memhouse": "./bin/memhouse.js"