memhouse 0.15.0 → 0.16.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/bin/memhouse.js CHANGED
@@ -30,6 +30,7 @@ const {
30
30
  const envfile = require(path.join(REPO_ROOT, 'memhouse', 'envfile'));
31
31
  const { consumeSecretChunk } = require(path.join(REPO_ROOT, 'memhouse', 'secret-input'));
32
32
  const { capabilitiesFrom } = require(path.join(REPO_ROOT, 'memhouse', 'capabilities'));
33
+ const { unknownFlags, allowedFlags, suggestFlag } = require(path.join(REPO_ROOT, 'memhouse', 'flags'));
33
34
 
34
35
  const HOME_DIR = process.env.MEMHOUSE_HOME || path.join(os.homedir(), '.memhouse');
35
36
  const ENV_FILE = path.join(HOME_DIR, 'env');
@@ -53,6 +54,22 @@ for (let i = 0; i < rest.length; i++) {
53
54
  }
54
55
  const JSON_OUT = flags.json === true;
55
56
 
57
+ // Unknown flags stop the run — see memhouse/flags.js for why a shrug is not enough.
58
+ (() => {
59
+ const bad = unknownFlags(cmd, flags);
60
+ if (!bad.length) return;
61
+ const allowed = allowedFlags(cmd);
62
+ for (const f of bad) {
63
+ const hint = suggestFlag(f, allowed);
64
+ console.error(`memhouse: unknown option --${f}${hint ? ` (did you mean --${hint}?)` : ''}`);
65
+ }
66
+ const list = [...allowed].sort().map((f) => `--${f}`).join(' ');
67
+ console.error(` ${cmd || 'memhouse'} takes: ${list || '(no options)'}`);
68
+ console.error(' Nothing ran. A flag that is ignored rather than refused is how a --dry-run typo');
69
+ console.error(' becomes a real migration.');
70
+ process.exit(2);
71
+ })();
72
+
56
73
  let ONBOARDING = false;
57
74
  let _inviteFileToShred = null;
58
75
  let _inviteWantsRotate = false;
@@ -2957,6 +2974,9 @@ async function cmdWhoami() {
2957
2974
  const au = process.env.MEMHOUSE_ADMIN_USER;
2958
2975
  const ap = process.env.MEMHOUSE_ADMIN_PASSWORD;
2959
2976
  const usingAdminEnv = wantAdmin && au;
2977
+ // `--admin` with nothing to resolve is worth saying out loud. Silently falling back to
2978
+ // the member credential makes `whoami --admin` and `whoami` print the same thing, and
2979
+ // the reader is left thinking they asked a question the program never heard.
2960
2980
  const who = usingAdminEnv ? { ...cfg, user: au, password: ap || '' } : cfg;
2961
2981
 
2962
2982
  if (!who.url || !who.user) {
@@ -2990,10 +3010,16 @@ async function cmdWhoami() {
2990
3010
  console.log(JSON.stringify({
2991
3011
  ok: true, url: who.url, user: server, db: who.db, source, role, ...caps,
2992
3012
  grants_readable: grants.length > 0,
3013
+ admin_env_present: Boolean(au),
3014
+ admin_requested: wantAdmin,
2993
3015
  }, null, 2));
2994
3016
  return 0;
2995
3017
  }
2996
3018
  console.log(ok(`${server} at ${who.url} — ${role}`));
3019
+ if (wantAdmin && !usingAdminEnv) {
3020
+ console.log(warn('--admin asked for MEMHOUSE_ADMIN_USER / MEMHOUSE_ADMIN_PASSWORD; neither is set,'));
3021
+ console.log(' so this is the ordinary configured credential.');
3022
+ }
2997
3023
  console.log(` credential from: ${source}`);
2998
3024
  console.log(` house: ${who.db || '(none set)'}`);
2999
3025
  console.log(` may create users: ${caps.canMintUsers ? 'yes' : 'no'}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mem",
3
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.15.0",
4
+ "version": "0.16.0",
5
5
  "author": {
6
6
  "name": "Ramazan Polat"
7
7
  },
@@ -0,0 +1,76 @@
1
+ // Which options each command takes, and what to say when one is not among them.
2
+ //
3
+ // Anything `--like-this` used to be accepted and ignored. That is quiet in the good case
4
+ // and dangerous in the bad one: `--dryrun` for `--dry-run` does not warn, it RUNS the
5
+ // migration; `--adopt` on a build that predates the guard is swallowed and the invite
6
+ // proceeds into somebody else's house. A flag the program does not know is a sentence
7
+ // the user believes they said and the program never heard.
8
+ //
9
+ // The tables live here rather than beside the parser so a test can hold them against the
10
+ // flags the CLI actually reads — a flag added to one and not the other fails the suite
11
+ // instead of being discovered by whoever typed it.
12
+
13
+ const GLOBAL_FLAGS = ['json', 'yes', 'help', 'version'];
14
+
15
+ const COMMAND_FLAGS = {
16
+ null: [], help: [], version: [],
17
+ whoami: ['admin'],
18
+ discover: [], stats: [], stop: [], doctor: [], 'sessions-query': [],
19
+ status: [], start: [],
20
+ onboard: ['url', 'user', 'password', 'db', 'port', 'no-ship', 'target', 'house-port', 'tag', 'local'],
21
+ install: ['url', 'user', 'password', 'db', 'port', 'no-ship', 'env', 'print-sql', 'member',
22
+ 'member-password', 'admin-user', 'admin-password', 'ensure-schema', 'rotate-password', 'force'],
23
+ setup: ['url', 'user', 'password', 'db', 'port'],
24
+ ship: ['full', 'loop', 'ensure-schema'],
25
+ search: ['limit'],
26
+ resume: [],
27
+ update: ['check', 'migrate', 'no-install'],
28
+ reset: ['all-origins'],
29
+ invite: ['url', 'db', 'out', 'admin-user', 'admin-password', 'adopt', 'print-sql',
30
+ 'allow-local', 'member-password'],
31
+ passwd: ['password', 'admin-user', 'admin-password', 'print-sql', 'member'],
32
+ nightly: ['out'],
33
+ migrate: ['dry-run'], 'migrate-rooms': ['dry-run'],
34
+ relocate: ['to', 'to-user', 'to-password', 'to-db', 'from-native-host', 'from-native-port',
35
+ 'insecure-native', 'keep-shipper', 'dry-run'],
36
+ deploy: ['local', 'down', 'house-port', 'port', 'tag'],
37
+ service: ['interval'],
38
+ uninstall: ['credentials', 'full-removal'],
39
+ plugins: ['target'],
40
+ prompt: ['install'],
41
+ };
42
+
43
+ /** Levenshtein, small and local — this is the only caller. */
44
+ function editDistance(a, b) {
45
+ const m = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
46
+ for (let j = 0; j <= b.length; j++) m[0][j] = j;
47
+ for (let i = 1; i <= a.length; i++) {
48
+ for (let j = 1; j <= b.length; j++) {
49
+ m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
50
+ }
51
+ }
52
+ return m[a.length][b.length];
53
+ }
54
+
55
+ /** The flag a typo most likely meant, or null when nothing is close enough to guess. */
56
+ function suggestFlag(bad, allowed) {
57
+ let best = null; let bd = Infinity;
58
+ for (const c of allowed) { const d = editDistance(bad, c); if (d < bd) { bd = d; best = c; } }
59
+ return bd <= Math.max(2, Math.floor(bad.length / 3)) ? best : null;
60
+ }
61
+
62
+ /** Every option this command does not take. Unknown COMMANDS return [] — dispatch owns that. */
63
+ function unknownFlags(cmd, flags) {
64
+ const known = COMMAND_FLAGS[cmd === null || cmd === undefined ? 'null' : cmd];
65
+ if (known === undefined) return [];
66
+ const allowed = allowedFlags(cmd);
67
+ return Object.keys(flags).filter((f) => !allowed.has(f));
68
+ }
69
+
70
+ /** The full accepted set for a command, globals included. */
71
+ function allowedFlags(cmd) {
72
+ const known = COMMAND_FLAGS[cmd === null || cmd === undefined ? 'null' : cmd] || [];
73
+ return new Set([...GLOBAL_FLAGS, ...known]);
74
+ }
75
+
76
+ module.exports = { GLOBAL_FLAGS, COMMAND_FLAGS, unknownFlags, allowedFlags, suggestFlag, editDistance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memhouse",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
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": {