memhouse 0.18.10 → 0.18.12
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/AGENTS.md +10 -11
- package/CHANGELOG.md +153 -13
- package/README.md +2 -2
- package/SECURITY.md +4 -2
- package/bin/memhouse.js +105 -49
- package/docs/guides/admin-password.md +1 -1
- package/docs/guides/configuration.md +2 -2
- package/docs/guides/install.md +1 -1
- package/docs/guides/invite.md +1 -1
- package/docs/guides/storage.md +10 -7
- package/docs/guides/uninstall.md +4 -2
- package/docs/releases/README.md +2 -0
- package/docs/releases/v0.18.10.md +7 -7
- package/docs/releases/v0.18.11.md +187 -0
- package/docs/releases/v0.18.12.md +59 -0
- package/docs/tutorials/01-first-house.md +2 -2
- package/editors/claude.js +70 -12
- package/editors/cursor-agent.js +1 -1
- package/examples/01-local-one-machine/README.md +4 -4
- package/examples/02-existing-clickhouse/README.md +7 -7
- package/memhouse/DESIGN.md +9 -2
- package/memhouse/RESUME-AND-UPDATE.md +1 -1
- package/memhouse/capabilities.js +1 -1
- package/memhouse/convert.js +3 -3
- package/memhouse/delivery/PROMPT.md +1 -1
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/memhouse/delivery/plugin/reference/HOUSE.md +21 -9
- package/memhouse/delivery/plugin/skills/access/SKILL.md +2 -2
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +3 -3
- package/memhouse/delivery/plugin/skills/house/SKILL.md +2 -2
- package/memhouse/delivery/plugin/skills/recall/SKILL.md +36 -16
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +2 -2
- package/memhouse/flags.js +28 -1
- package/memhouse/host.js +11 -6
- package/memhouse/house/HOUSE.md +6 -6
- package/memhouse/house/house.js +31 -5
- package/memhouse/invite-target.js +37 -0
- package/memhouse/self-update.js +1 -1
- package/memhouse/server/queries.js +11 -6
- package/memhouse/shipper/ship.js +148 -31
- package/package.json +3 -3
- package/public/THIRD_PARTY_NOTICES.txt +2114 -0
- package/ui/README.md +16 -0
- package/ui/vite.config.js +4 -1
package/bin/memhouse.js
CHANGED
|
@@ -42,17 +42,7 @@ const LOG_DIR = path.join(HOME_DIR, 'logs');
|
|
|
42
42
|
const argv = process.argv.slice(2);
|
|
43
43
|
const cmd = argv[0] && !argv[0].startsWith('-') ? argv[0] : null;
|
|
44
44
|
const rest = cmd ? argv.slice(1) : argv;
|
|
45
|
-
const flags =
|
|
46
|
-
const positional = [];
|
|
47
|
-
for (let i = 0; i < rest.length; i++) {
|
|
48
|
-
const a = rest[i];
|
|
49
|
-
if (a.startsWith('--')) {
|
|
50
|
-
const key = a.slice(2);
|
|
51
|
-
const next = rest[i + 1];
|
|
52
|
-
if (next !== undefined && !next.startsWith('--')) { flags[key] = next; i++; }
|
|
53
|
-
else flags[key] = true;
|
|
54
|
-
} else positional.push(a);
|
|
55
|
-
}
|
|
45
|
+
const { flags, positional } = require(path.join(REPO_ROOT, 'memhouse', 'flags')).parseArgv(rest);
|
|
56
46
|
const JSON_OUT = flags.json === true;
|
|
57
47
|
|
|
58
48
|
// Unknown flags stop the run — see memhouse/flags.js for why a shrug is not enough.
|
|
@@ -90,7 +80,7 @@ function resolveConfig() {
|
|
|
90
80
|
url: pick('url', 'MEMHOUSE_URL', 'http://localhost:8123'),
|
|
91
81
|
user: pick('user', 'MEMHOUSE_USER', 'memhouse_root'),
|
|
92
82
|
password: pick('password', 'MEMHOUSE_PASSWORD', ''),
|
|
93
|
-
// The house defaults to the USER'S OWN NAME (`
|
|
83
|
+
// The house defaults to the USER'S OWN NAME (`sam` ships into `sam.messages`),
|
|
94
84
|
// because a house is a database and the natural first house is your own. Any name
|
|
95
85
|
// works — user 'alice' with house 'default' is a supported pairing.
|
|
96
86
|
// ONE house name unless somebody has a reason. The member's rooms are named for them
|
|
@@ -341,6 +331,22 @@ async function ask(question, dflt) {
|
|
|
341
331
|
// reason in .cause. `✗ not connected: fetch failed` names neither the host nor the problem,
|
|
342
332
|
// while `ship` prints ECONNREFUSED for the identical condition — the three commands
|
|
343
333
|
// disagreed about the same event.
|
|
334
|
+
// Is `url` an HTTP endpoint that answers at all? ClickHouse's /ping needs no credential, so
|
|
335
|
+
// this checks an address without sending one. Any HTTP response counts — a proxy that
|
|
336
|
+
// wants auth on every path still proves the address is reachable.
|
|
337
|
+
async function pingUrl(url) {
|
|
338
|
+
let target;
|
|
339
|
+
try { target = new URL('ping', String(url).endsWith('/') ? String(url) : `${url}/`); }
|
|
340
|
+
catch { return { ok: false, reason: 'not a URL' }; }
|
|
341
|
+
try {
|
|
342
|
+
await fetch(target, { signal: AbortSignal.timeout(8000), redirect: 'manual' });
|
|
343
|
+
return { ok: true };
|
|
344
|
+
} catch (e) {
|
|
345
|
+
const c = e && e.cause && (e.cause.code || e.cause.message);
|
|
346
|
+
return { ok: false, reason: c || (e && e.name === 'TimeoutError' ? 'timed out' : String(e && e.message || e)) };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
344
350
|
function netReason(e) {
|
|
345
351
|
const cause = e && e.cause;
|
|
346
352
|
const inner = cause && (cause.message || cause.code);
|
|
@@ -358,7 +364,7 @@ function netReason(e) {
|
|
|
358
364
|
// --admin-password (warned), then MEMHOUSE_ADMIN_PASSWORD, then the stored credential
|
|
359
365
|
// of that SAME admin, then a TTY prompt. null = none, and the caller has been told how
|
|
360
366
|
// to give one without putting it on the command line. See memhouse/admin-secret.js.
|
|
361
|
-
async function adminPasswordFor(adminUser) {
|
|
367
|
+
async function adminPasswordFor(adminUser, out = {}) {
|
|
362
368
|
const as = require(path.join(REPO_ROOT, 'memhouse', 'admin-secret'));
|
|
363
369
|
const file = readEnvFile();
|
|
364
370
|
let r;
|
|
@@ -369,9 +375,11 @@ async function adminPasswordFor(adminUser) {
|
|
|
369
375
|
} catch (e) { console.log(bad(e.message)); return null; }
|
|
370
376
|
// stderr: `members --json` and friends must emit nothing but JSON on stdout.
|
|
371
377
|
if (r.source === 'flag') console.error(warn(as.ARGV_WARNING));
|
|
378
|
+
out.source = r.source;
|
|
372
379
|
if (r.password !== undefined) return r.password;
|
|
373
380
|
if (process.stdin.isTTY) {
|
|
374
381
|
const p = await askSecret(` password for '${adminUser}'`);
|
|
382
|
+
out.source = 'prompt';
|
|
375
383
|
if (p) return p;
|
|
376
384
|
console.log(bad('no password given'));
|
|
377
385
|
return null;
|
|
@@ -762,7 +770,7 @@ function printLocalHouseOffer(engines) {
|
|
|
762
770
|
// The ClickHouse user IS the identity; rooms are named for whatever the server answers
|
|
763
771
|
// to currentUser(). The home directory only produces a SUGGESTION, and a suggestion that
|
|
764
772
|
// had to be changed is printed rather than applied quietly — sanitizing collides
|
|
765
|
-
// (`
|
|
773
|
+
// (`jane.doe` and `jane-doe` both land on `jane_doe`), so the human has to
|
|
766
774
|
// see it.
|
|
767
775
|
function suggestMember() {
|
|
768
776
|
const base = path.basename(os.homedir() || '');
|
|
@@ -1079,7 +1087,7 @@ async function cmdInstall({ interactive }) {
|
|
|
1079
1087
|
for (const k of wanted) process.env[k] = parsed[k];
|
|
1080
1088
|
// The invite also carries what the inviter's house is ON: its channel (a `team` house
|
|
1081
1089
|
// is 0.18.x, published under `team`). Dropping it wrote an env with no pin, and the
|
|
1082
|
-
// member's first `memhouse update` followed `latest` —
|
|
1090
|
+
// member's first `memhouse update` followed `latest` — then the 0.17 line — and
|
|
1083
1091
|
// downgraded a working team install onto the wrong layout. A flag still wins.
|
|
1084
1092
|
for (const k of ['MEMHOUSE_CHANNEL', 'MEMHOUSE_EDITORS', 'MEMHOUSE_CLAUDE_ROOTS']) if (parsed[k]) process.env[k] = parsed[k];
|
|
1085
1093
|
console.log(ok(`using the invite file ${String(flags.env)} (nothing persisted until the install proves out)`));
|
|
@@ -2248,15 +2256,31 @@ function installKind() {
|
|
|
2248
2256
|
async function cmdUpdate() {
|
|
2249
2257
|
const { kind, root } = installKind();
|
|
2250
2258
|
const cfg0 = resolveConfig();
|
|
2251
|
-
|
|
2252
|
-
//
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
const pinned = flags.channel && flags.channel !== true ? String(flags.channel) : (cfg0.channel || null);
|
|
2259
|
+
const asked = flags.channel && flags.channel !== true ? String(flags.channel) : null;
|
|
2260
|
+
// What is PINNED is what the env file says. resolveConfig folds --channel into cfg0, so
|
|
2261
|
+
// comparing the flag with cfg0.channel compared it with itself, and the pin was never
|
|
2262
|
+
// written at all.
|
|
2263
|
+
const stored = readEnvFile().MEMHOUSE_CHANNEL || null;
|
|
2264
|
+
const pinned = asked || cfg0.channel || null;
|
|
2258
2265
|
const tags = await chan.fetchTags();
|
|
2259
2266
|
const pick = chan.pickChannel({ version: PKG.version, pinned, tags });
|
|
2267
|
+
// --channel pins for this run AND every later one: the point of pinning is that the next
|
|
2268
|
+
// person to type `memhouse update` does not have to remember it. Only a channel the
|
|
2269
|
+
// registry actually has is pinned. The pin used to be written before anything was
|
|
2270
|
+
// checked, so `--channel tema` (or any typo) left every later `update` following a tag
|
|
2271
|
+
// that does not exist, --no-install included, which checks no channel at all (A5).
|
|
2272
|
+
if (asked && asked !== stored) {
|
|
2273
|
+
if (!tags) {
|
|
2274
|
+
console.log(warn(`the registry did not answer, so channel '${asked}' cannot be checked — not pinned`));
|
|
2275
|
+
} else if (!tags[asked]) {
|
|
2276
|
+
console.log(bad(`no channel '${asked}' on the registry — nothing pinned, nothing changed`));
|
|
2277
|
+
console.log(` channels: ${Object.keys(tags).join(', ')}`);
|
|
2278
|
+
return 1;
|
|
2279
|
+
} else {
|
|
2280
|
+
writeEnvFile({ ...cfg0, channel: asked });
|
|
2281
|
+
console.log(ok(`channel pinned in ${short(ENV_FILE)}: ${asked}`));
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2260
2284
|
const latest = pick.target;
|
|
2261
2285
|
if (JSON_OUT && flags.check) return console.log(JSON.stringify({ kind, root, current: PKG.version, channel: pick.channel, target: pick.target, reason: pick.reason, tags }, null, 2));
|
|
2262
2286
|
console.log(` installed ${PKG.version} (${kind}: ${root})`);
|
|
@@ -2264,12 +2288,18 @@ async function cmdUpdate() {
|
|
|
2264
2288
|
console.log(` target ${pick.target || (tags ? 'nothing to install' : 'unknown — the registry did not answer')}`);
|
|
2265
2289
|
if (latest && latest === PKG.version && kind !== 'checkout') console.log(ok('already current'));
|
|
2266
2290
|
if (flags.check) return 0;
|
|
2267
|
-
|
|
2291
|
+
// --no-install asks for the restart and the house checks only; the files are already in
|
|
2292
|
+
// place, so which channel they came from does not matter. Refusing here for want of one
|
|
2293
|
+
// made the command every nightly prints for itself ("memhouse update --no-install")
|
|
2294
|
+
// return 1 on exactly the builds that print it — a tarball is on no registry tag — and
|
|
2295
|
+
// leave the old shipper running the old code.
|
|
2296
|
+
const noInstall = flags['no-install'] === true;
|
|
2297
|
+
if (!pick.channel && kind !== 'checkout' && !noInstall) {
|
|
2268
2298
|
console.log(warn('no automatic update for this install'));
|
|
2269
2299
|
console.log(` ${pick.reason}`);
|
|
2270
2300
|
return 1;
|
|
2271
2301
|
}
|
|
2272
|
-
if (pick.channel && !pick.target && kind !== 'checkout') {
|
|
2302
|
+
if (pick.channel && !pick.target && kind !== 'checkout' && !noInstall) {
|
|
2273
2303
|
console.log(bad(`channel '${pick.channel}' has no version on the registry — nothing to install, nothing changed`));
|
|
2274
2304
|
return 1;
|
|
2275
2305
|
}
|
|
@@ -2279,7 +2309,9 @@ async function cmdUpdate() {
|
|
|
2279
2309
|
console.log(' to keep a version around: npm install -g memhouse');
|
|
2280
2310
|
return 1;
|
|
2281
2311
|
}
|
|
2282
|
-
|
|
2312
|
+
// --no-install installs nothing, so where the files came from does not matter: restart and
|
|
2313
|
+
// check the house. The refusal used to come first and turned it away (A6).
|
|
2314
|
+
if (kind === 'local-dep' && !noInstall) {
|
|
2283
2315
|
console.log(warn('this is a project dependency, not a global install'));
|
|
2284
2316
|
console.log(` upgrade it where it lives: npm install memhouse@${pick.channel || 'latest'}`);
|
|
2285
2317
|
return 1;
|
|
@@ -2292,7 +2324,7 @@ async function cmdUpdate() {
|
|
|
2292
2324
|
try { svc = require(path.join(REPO_ROOT, 'memhouse', 'service.js')).status(); } catch { /* unsupported platform */ }
|
|
2293
2325
|
const wasRunning = { shipper: !!pidOf('shipper'), dashboard: !!pidOf('dashboard') };
|
|
2294
2326
|
|
|
2295
|
-
if (
|
|
2327
|
+
if (noInstall) {
|
|
2296
2328
|
// The files were already replaced by other means — a hand-typed `npm i -g`, a tarball,
|
|
2297
2329
|
// a configuration manager. The npm/git half is exactly what such a pilot has already
|
|
2298
2330
|
// done, and the half a bare install leaves undone (restart, migrations, schema heal)
|
|
@@ -2326,7 +2358,9 @@ async function cmdUpdate() {
|
|
|
2326
2358
|
return 1;
|
|
2327
2359
|
}
|
|
2328
2360
|
}
|
|
2329
|
-
|
|
2361
|
+
// Said only when something was installed. Under --no-install it claimed "files updated"
|
|
2362
|
+
// for files nobody touched (A6).
|
|
2363
|
+
if (!noInstall) console.log(ok('files updated'));
|
|
2330
2364
|
// The house may have been converted to the one layout since this machine last ran:
|
|
2331
2365
|
// the member's rooms moved from <name>.* to mem.<name>_*. Fix the env here, before the
|
|
2332
2366
|
// shipper restarts, or its first pass refuses the old address forever.
|
|
@@ -2466,7 +2500,7 @@ async function cmdUpdate() {
|
|
|
2466
2500
|
// Every Claude Code instance on this machine, not just the default one.
|
|
2467
2501
|
//
|
|
2468
2502
|
// A pilot rarely has one. `~/.claude` is the stock install, `CLAUDE_CONFIG_DIR` points at
|
|
2469
|
-
// whichever they are running right now, and
|
|
2503
|
+
// whichever they are running right now, and playbook installs live under
|
|
2470
2504
|
// `~/.claude-playbooks/<name>[/playbook]`, each a complete config directory with its own
|
|
2471
2505
|
// skills/. Installing into one and calling it done leaves /mem:recall missing from
|
|
2472
2506
|
// every other instance the pilot uses — silently, because a missing skill does not announce
|
|
@@ -3709,14 +3743,36 @@ async function cmdInvite() {
|
|
|
3709
3743
|
// itself is tried below — which works for whoever set the house up as its superuser.
|
|
3710
3744
|
if (flags['admin-user'] === true) { console.log(bad('--admin-user needs a name, e.g. --admin-user default')); return 1; }
|
|
3711
3745
|
let adminUser = (flags['admin-user'] ? String(flags['admin-user']) : '') || cfg.adminUser || undefined;
|
|
3746
|
+
// Who named this admin decides where its credential may go (G4, memhouse/invite-target.js):
|
|
3747
|
+
// one given for this command provisions at --url; one this install keeps provisions only
|
|
3748
|
+
// at the house it was saved for.
|
|
3749
|
+
const adminUserSource = flags['admin-user'] ? 'flag'
|
|
3750
|
+
: (process.env.MEMHOUSE_ADMIN_USER ? 'env' : (adminUser ? 'file' : 'member'));
|
|
3712
3751
|
let adminPass;
|
|
3752
|
+
const pwSource = {};
|
|
3713
3753
|
if (adminUser) {
|
|
3714
3754
|
// --admin-password-file / --admin-password / MEMHOUSE_ADMIN_PASSWORD / the stored one
|
|
3715
3755
|
// for this same admin / a prompt. `--admin-user` as a flag used to switch the
|
|
3716
3756
|
// environment off, so the only scriptable path put the password on argv.
|
|
3717
|
-
adminPass = await adminPasswordFor(adminUser);
|
|
3757
|
+
adminPass = await adminPasswordFor(adminUser, pwSource);
|
|
3718
3758
|
if (adminPass === null) return 1;
|
|
3719
3759
|
}
|
|
3760
|
+
const target = require(path.join(REPO_ROOT, 'memhouse', 'invite-target'))
|
|
3761
|
+
.provisionTarget({ url, storedUrl: configuredUrl, adminUserSource, passwordSource: pwSource.source || null });
|
|
3762
|
+
if (target.error) { console.log(bad(target.error)); return 1; }
|
|
3763
|
+
// A stored credential is never sent to --url, so check that the INVITEE can reach it
|
|
3764
|
+
// without one: ClickHouse answers /ping unauthenticated.
|
|
3765
|
+
if (target.stored) {
|
|
3766
|
+
const reach = await pingUrl(url);
|
|
3767
|
+
if (!reach.ok) {
|
|
3768
|
+
console.log(bad(`cannot reach ${url}: ${reach.reason}`));
|
|
3769
|
+
console.log(' --url is the address the INVITEE will use, and invite checks it from here first.');
|
|
3770
|
+
console.log(' A `deploy --local` house is bound to loopback on purpose, so no LAN address');
|
|
3771
|
+
console.log(' reaches it and no invitee could either — publish it (tunnel, reverse proxy)');
|
|
3772
|
+
console.log(' before inviting, or use --print-sql and let the invitee be told the address.');
|
|
3773
|
+
return 1;
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3720
3776
|
if (!adminUser) {
|
|
3721
3777
|
const c = requireConfig(cfg, 'invite');
|
|
3722
3778
|
if (!c.user) return 1;
|
|
@@ -3735,20 +3791,18 @@ async function cmdInvite() {
|
|
|
3735
3791
|
// unreachable host — a typo, a loopback-bound house, a tunnel that is down — come back
|
|
3736
3792
|
// as "you are only a member", which sends the reader after a privilege they already
|
|
3737
3793
|
// have. Caught on the testbed, where a wrong --url produced exactly that.
|
|
3794
|
+
// The member credential is stored, so it is checked against the house it was saved for
|
|
3795
|
+
// (target.url), never against --url; --url's reachability was checked above, unauthenticated.
|
|
3738
3796
|
try {
|
|
3739
|
-
await ch({ ...c, url }, 'SELECT 1', { database: '' });
|
|
3797
|
+
await ch({ ...c, url: target.url }, 'SELECT 1', { database: '' });
|
|
3740
3798
|
} catch (e) {
|
|
3741
|
-
console.log(bad(`cannot reach ${url} as '${c.user}': ${netReason(e)}`));
|
|
3742
|
-
console.log(' --url is the address the INVITEE will use, and invite checks it from here first.');
|
|
3743
|
-
console.log(' A `deploy --local` house is bound to loopback on purpose, so no LAN address');
|
|
3744
|
-
console.log(' reaches it and no invitee could either — publish it (tunnel, reverse proxy)');
|
|
3745
|
-
console.log(' before inviting, or use --print-sql and let the invitee be told the address.');
|
|
3799
|
+
console.log(bad(`cannot reach your house ${target.url} as '${c.user}': ${netReason(e)}`));
|
|
3746
3800
|
return 1;
|
|
3747
3801
|
}
|
|
3748
3802
|
// What this credential may do, judged from its own grants and SCOPE-AWARE — a member
|
|
3749
3803
|
// holds CREATE DATABASE inside `ON <their-db>.*`, which mints no new house at all.
|
|
3750
3804
|
// Shared with `whoami` so the two can never disagree about who is an administrator.
|
|
3751
|
-
const caps = capabilitiesFrom(await readGrants({ ...c, url }, c.user));
|
|
3805
|
+
const caps = capabilitiesFrom(await readGrants({ ...c, url: target.url }, c.user));
|
|
3752
3806
|
if (caps.canProvision) {
|
|
3753
3807
|
adminUser = c.user; adminPass = c.password;
|
|
3754
3808
|
console.log(ok(`inviting as your own credential '${c.user}' (it can create users and houses here)`));
|
|
@@ -3771,13 +3825,12 @@ async function cmdInvite() {
|
|
|
3771
3825
|
}
|
|
3772
3826
|
}
|
|
3773
3827
|
|
|
3774
|
-
// WHERE to connect to provision
|
|
3775
|
-
//
|
|
3776
|
-
//
|
|
3777
|
-
//
|
|
3778
|
-
//
|
|
3779
|
-
|
|
3780
|
-
const provisionUrl = flags['admin-user'] ? url : cfg.url;
|
|
3828
|
+
// WHERE to connect to provision: decided above by provisionTarget(). A stored credential
|
|
3829
|
+
// provisions at the house it was saved for; one given for this command, at --url. Either
|
|
3830
|
+
// way the invite FILE carries --url, the invitee's path. (This used to read
|
|
3831
|
+
// `flags['admin-user'] ? url : cfg.url` — but cfg.url IS --url once the flag is given, so
|
|
3832
|
+
// the stored credential went to whatever --url named.)
|
|
3833
|
+
const provisionUrl = target.url;
|
|
3781
3834
|
|
|
3782
3835
|
// Is there already a house here, with somebody's memory in it? The house is created
|
|
3783
3836
|
// with CREATE DATABASE IF NOT EXISTS, so inviting a name whose database already exists
|
|
@@ -4037,8 +4090,10 @@ async function cmdUninstall() {
|
|
|
4037
4090
|
//
|
|
4038
4091
|
// (default) daemons, service, runtime state. Config and identity KEPT.
|
|
4039
4092
|
// --credentials also forget the house: the env file, with its password.
|
|
4040
|
-
// --full-removal all of MEMHOUSE_HOME,
|
|
4041
|
-
//
|
|
4093
|
+
// --full-removal all of MEMHOUSE_HOME, host.json included. The next install
|
|
4094
|
+
// derives the same fingerprint from the OS machine id (0.18.6+), so it
|
|
4095
|
+
// is the same host unless the machine was renamed meanwhile or has no
|
|
4096
|
+
// readable machine id (then it falls back to random: a new host).
|
|
4042
4097
|
//
|
|
4043
4098
|
// No tier touches the house: transcripts live in ClickHouse and re-ship from the local
|
|
4044
4099
|
// session stores regardless.
|
|
@@ -4059,8 +4114,9 @@ async function cmdUninstall() {
|
|
|
4059
4114
|
if (flags.yes !== true && process.stdin.isTTY) {
|
|
4060
4115
|
const id = hostjs.read(HOME_DIR);
|
|
4061
4116
|
console.log(warn(`--full-removal also discards this machine's host identity${id ? ` (${id.id})` : ''}.`));
|
|
4062
|
-
console.log('
|
|
4063
|
-
console.log(
|
|
4117
|
+
console.log(' A reinstall derives it again from this machine\'s own id, so it normally comes');
|
|
4118
|
+
console.log(' back the same — unless the machine is renamed first, or has no readable machine');
|
|
4119
|
+
console.log(" id, and then it reads as two machines. Your transcripts themselves are safe.");
|
|
4064
4120
|
const a = (await ask('Remove everything, including the host identity? (yes/no)', 'no')).toLowerCase();
|
|
4065
4121
|
if (a !== 'yes' && a !== 'y') { console.log('aborted — daemons are stopped, nothing was removed'); return; }
|
|
4066
4122
|
}
|
|
@@ -4556,7 +4612,7 @@ async function cmdUninstall() {
|
|
|
4556
4612
|
process.exitCode = 2; break;
|
|
4557
4613
|
}
|
|
4558
4614
|
// Who owns this house. A local deploy is the pilot's own machine and their own
|
|
4559
|
-
// memory, so the house user is THEM — suggested from the OS username (`
|
|
4615
|
+
// memory, so the house user is THEM — suggested from the OS username (`sam`, not
|
|
4560
4616
|
// `memhouse_root`) — created by the container itself at first init with full
|
|
4561
4617
|
// rights. No admin/member split, no provisioning dance: a URI and a credential is
|
|
4562
4618
|
// the whole install. The name is fixed at volume init exactly like the password,
|
|
@@ -4617,7 +4673,7 @@ async function cmdUninstall() {
|
|
|
4617
4673
|
// STATED sources only, then the house user. priorCfg.db is unusable here: resolveConfig
|
|
4618
4674
|
// computes a DEFAULT for it (the user's name, itself defaulted), so going through it
|
|
4619
4675
|
// handed a fresh deploy `memhouse_root` as the house name while the superuser was
|
|
4620
|
-
// '
|
|
4676
|
+
// 'sam' — the memory would live in a database named after a role account nobody
|
|
4621
4677
|
// chose.
|
|
4622
4678
|
const targetDb = flags.db || process.env.MEMHOUSE_DB || readEnvFile().MEMHOUSE_DB || 'mem';
|
|
4623
4679
|
// Same check install runs, but EARLY — deploy initialises the container volume and
|
|
@@ -40,7 +40,7 @@ options. It never hangs, and it never guesses.
|
|
|
40
40
|
The best form: the value exists only in the one command's environment.
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:
|
|
43
|
+
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:house-admin -- \
|
|
44
44
|
memhouse invite alice --url https://house.example.com --admin-user default
|
|
45
45
|
|
|
46
46
|
# 1Password
|
|
@@ -33,7 +33,7 @@ it:
|
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
Watched directories (override with the variable shown, in the env file):
|
|
36
|
-
claude ~/.claude, ~/.claude-playbooks/
|
|
36
|
+
claude ~/.claude, ~/.claude-playbooks/work, … [MEMHOUSE_CLAUDE_ROOTS]
|
|
37
37
|
codex ~/.codex [MEMHOUSE_CODEX_ROOTS]
|
|
38
38
|
gemini-cli ~/.gemini [MEMHOUSE_GEMINI_CLI_ROOTS]
|
|
39
39
|
cursor ~/Library/Application Support/Cursor/User, … [built-in]
|
|
@@ -43,7 +43,7 @@ Two kinds of line in the env file narrow it, or `--editors` / `--claude-roots` a
|
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
MEMHOUSE_EDITORS='claude'
|
|
46
|
-
MEMHOUSE_CLAUDE_ROOTS='~/.claude-playbooks/
|
|
46
|
+
MEMHOUSE_CLAUDE_ROOTS='~/.claude-playbooks/work-team'
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
- `MEMHOUSE_EDITORS` names the adapters to run.
|
package/docs/guides/install.md
CHANGED
|
@@ -64,7 +64,7 @@ MEMHOUSE_PASSWORD=… memhouse install --url https://house.example.com --user al
|
|
|
64
64
|
memhouse install --env invite-alice.env
|
|
65
65
|
|
|
66
66
|
# you hold the server's admin: memhouse creates the member, then ships as the member
|
|
67
|
-
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:
|
|
67
|
+
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:house-admin -- \
|
|
68
68
|
memhouse install --url https://house.example.com --admin-user default --member alice
|
|
69
69
|
|
|
70
70
|
# you hold nothing: print the SQL for whoever administers the server
|
package/docs/guides/invite.md
CHANGED
|
@@ -33,7 +33,7 @@ memhouse invite alice --url https://house.example.com
|
|
|
33
33
|
`--admin-password-file`, or from a prompt.
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:
|
|
36
|
+
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:house-admin -- \
|
|
37
37
|
memhouse invite alice --url https://house.example.com --admin-user default
|
|
38
38
|
```
|
|
39
39
|
|
package/docs/guides/storage.md
CHANGED
|
@@ -23,8 +23,8 @@ database once leaked into each other. A member can read their isolation back wit
|
|
|
23
23
|
|
|
24
24
|
| | rooms | each member holds |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| alone on a laptop | `mem.
|
|
27
|
-
| a team on one server | `mem.
|
|
26
|
+
| alone on a laptop | `mem.sam_*` | one grant on `mem.sam_*` |
|
|
27
|
+
| a team on one server | `mem.sam_*`, `mem.alice_*`, … | one grant each, on their own |
|
|
28
28
|
|
|
29
29
|
Reading across members is a `UNION ALL` over the rooms you hold: your own, and any shared
|
|
30
30
|
with you. Naming a room you were not granted is an error, never a silent omission.
|
|
@@ -34,11 +34,14 @@ with you. Naming a room you were not granted is an error, never a silent omissio
|
|
|
34
34
|
- **`user_id`** is stamped by the server: `MATERIALIZED currentUser()`. Async inserts are
|
|
35
35
|
pinned off for members, so the stamp cannot be skipped. `WHERE user_id = 'alice'` is one
|
|
36
36
|
person.
|
|
37
|
-
- **`host`** is the machine.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
37
|
+
- **`host`** is the machine. Every row carries `<hostname>-<8 hex>`, where the 8 hex come
|
|
38
|
+
from a fingerprint of the OS's own machine id (IOPlatformUUID on macOS,
|
|
39
|
+
`/etc/machine-id` on Linux), hashed so the raw id never leaves the machine. It is kept in
|
|
40
|
+
`$MEMHOUSE_HOME/host.json`. It is not taken from the hostname:
|
|
41
|
+
- two laptops that both answer to `MacBook-Pro` still get different ids;
|
|
42
|
+
- a renamed machine keeps its id, and `status` reports the new name beside it;
|
|
43
|
+
- reinstalling, even after `uninstall --full-removal`, brings the same machine back
|
|
44
|
+
under the same id. Where no machine id can be read, it falls back to a random one.
|
|
42
45
|
|
|
43
46
|
`memhouse status` shows this machine's id. `memhouse doctor` names every host in your
|
|
44
47
|
rooms.
|
package/docs/guides/uninstall.md
CHANGED
|
@@ -12,8 +12,10 @@ npm uninstall -g memhouse # then remove the package
|
|
|
12
12
|
- **The default is the conservative one.** Stopping the shipper is routine: before an
|
|
13
13
|
upgrade, while debugging, or when a laptop should go quiet for a week. It should not
|
|
14
14
|
also mean "forget which house I use and who this machine is".
|
|
15
|
-
- **`--full-removal` asks first.** A later reinstall
|
|
16
|
-
|
|
15
|
+
- **`--full-removal` asks first.** It deletes `host.json`. A later reinstall derives the
|
|
16
|
+
same identity again from the machine's own id, so your history stays one machine, unless
|
|
17
|
+
the machine was renamed in between or has no readable machine id. Then it comes back as
|
|
18
|
+
a new host.
|
|
17
19
|
- **Your memory stays.** The rooms in ClickHouse are untouched by every tier. What the
|
|
18
20
|
shipper wrote can be rebuilt from the local session stores with `memhouse ship --full`,
|
|
19
21
|
for as long as the editors keep them.
|
package/docs/releases/README.md
CHANGED
|
@@ -5,6 +5,8 @@ do, how to upgrade, what is deprecated and when it goes, and what was deferred.
|
|
|
5
5
|
|
|
6
6
|
| version | date | headline |
|
|
7
7
|
|---|---|---|
|
|
8
|
+
| [0.18.12](v0.18.12.md) | 2026-10-29 | skills: allowed tools restricted to read-only commands |
|
|
9
|
+
| [0.18.11](v0.18.11.md) | 2026-10-10 | sessions with subagents stop re-shipping; security: no stored credential sent to `invite --url` |
|
|
8
10
|
| [0.18.10](v0.18.10.md) | 2026-10-08 | security: the admin password never on argv; no member password printed; a scoped share reaches only its scope |
|
|
9
11
|
|
|
10
12
|
Earlier versions: [`CHANGELOG.md`](../../CHANGELOG.md).
|
|
@@ -8,7 +8,7 @@ A security release:
|
|
|
8
8
|
|
|
9
9
|
Everyone on 0.18.x should upgrade.
|
|
10
10
|
|
|
11
|
-
**No drill was run for this release; waived by the
|
|
11
|
+
**No drill was run for this release; waived by the maintainer (2026-10-08). The drill runs before 0.19.0.**
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
memhouse update # or: npm install -g memhouse@latest, then memhouse update --no-install
|
|
@@ -34,14 +34,14 @@ The admin-assisted commands (`install --member`, `invite`, `passwd`, `convert`,
|
|
|
34
34
|
5. a prompt, when there is a terminal to prompt on.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:
|
|
37
|
+
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:house-admin -- \
|
|
38
38
|
memhouse invite alice --url https://house.example.com --admin-user default
|
|
39
39
|
printf '%s' "$ADMIN_PASSWORD" | memhouse invite alice --url … --admin-user default --admin-password-file -
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Before this release, `--admin-user` as a flag switched every other source off. The only
|
|
43
43
|
scriptable admin path therefore put the password on argv, where `ps` shows it for as long
|
|
44
|
-
as the command runs and shell history keeps it. Found
|
|
44
|
+
as the command runs and shell history keeps it. Found in a sandbox rehearsal (finding 6).
|
|
45
45
|
With no source and no terminal, the command stops and prints the three ways above instead
|
|
46
46
|
of hanging or guessing.
|
|
47
47
|
|
|
@@ -179,7 +179,7 @@ before release by four models. Three said "ship with fixes"; the fourth said "do
|
|
|
179
179
|
mostly for pre-existing issues. Its pre-tag fixes are not all done, and this release was
|
|
180
180
|
not held for them.
|
|
181
181
|
|
|
182
|
-
Status of the 0.18.10 pre-tag list (
|
|
182
|
+
Status of the 0.18.10 pre-tag list (the consolidated pre-tag review):
|
|
183
183
|
|
|
184
184
|
| item | what | status |
|
|
185
185
|
|---|---|---|
|
|
@@ -197,7 +197,7 @@ Status of the 0.18.10 pre-tag list (`review-0.18.10-consolidated`):
|
|
|
197
197
|
| G4 | `invite --url` connects with the stored credential to the invitee-facing URL | deferred to 0.18.11 (security) |
|
|
198
198
|
| G26 | skills point at `../reference/HOUSE.md`; the file is `../../reference/HOUSE.md` | deferred to 0.18.11 |
|
|
199
199
|
| G11 | an idle house's dashboard goes empty after the stats TTL | deferred to 0.18.11 |
|
|
200
|
-
|
|
|
200
|
+
| SUB-1 | a subagent file read before its first line is written re-sorts its siblings | deferred to 0.18.11 (with the RC) |
|
|
201
201
|
|
|
202
202
|
## Documentation
|
|
203
203
|
|
|
@@ -219,8 +219,8 @@ every tag and what it changed.
|
|
|
219
219
|
- CI is green on `main` at the release commit: the syntax gate, unit tests, the
|
|
220
220
|
one-layout and invite matrices, and a headless e2e on ClickHouse 25.11 and 26.7.
|
|
221
221
|
- No live house was touched to prepare this release.
|
|
222
|
-
- **No drill was run for this release; waived by the
|
|
222
|
+
- **No drill was run for this release; waived by the maintainer (2026-10-08). The drill runs before 0.19.0.**
|
|
223
223
|
`AGENTS.md` asks for a drill before a release that changes a surface a person touches,
|
|
224
224
|
and this one changes install, invite and share. A drill was prepared on the testbed, but
|
|
225
|
-
its setup
|
|
225
|
+
its setup could not run in the release environment, and the maintainer chose to release
|
|
226
226
|
without it.
|