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
|
|
80
|
-
|
|
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
|
|
671
|
-
-- the boundary — and it
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
--
|
|
675
|
-
--
|
|
676
|
-
|
|
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
|
-
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
|
|
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:
|
|
4
|
-
"version": "0.
|
|
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
|
|
34
|
-
|
|
35
|
-
whoever set the house up, so no
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
4
|
-
"description": "memhouse
|
|
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"
|