memhouse 0.3.1 → 0.3.2

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
@@ -42,6 +42,25 @@ install scripts by default, so without the flag those five read nothing and you
42
42
  silently ship a partial history. `memhouse discover` and `memhouse doctor` both
43
43
  say so when the binding is missing.
44
44
 
45
+ If that install fails with `EACCES`, `sudo` is the right answer for only one of the
46
+ two causes, and the thing that tells them apart is **who owns the prefix root** —
47
+ not the path, and not the failing file. The path proves nothing: Homebrew's prefix
48
+ is `/usr/local` on Intel and `/opt/homebrew` on Apple Silicon, and is yours in both
49
+ cases. The failing file proves nothing either: an earlier `sudo npm` leaves
50
+ root-owned files *inside* a prefix that is still yours.
51
+
52
+ ```bash
53
+ ls -ld "$(npm prefix -g)" # the prefix ROOT — this is the deciding one
54
+ ```
55
+
56
+ - **Prefix root owned by `root`** — a genuinely system-managed Node (distro
57
+ packages under `/usr`). Re-run with `sudo`.
58
+ - **Prefix root owned by you** — the prefix is yours (Homebrew, fnm, nvm, volta),
59
+ so whatever root-owned file npm tripped on is a stray from an earlier `sudo npm`.
60
+ Another `sudo` adds more of them. Repair just that path:
61
+ `sudo chown -R "$(id -u):$(id -g)" <the path npm named>`. The same applies to the
62
+ cache (`~/.npm`).
63
+
45
64
  To try it without installing, npx takes the same flag — it has to come before the
46
65
  package name:
47
66
 
package/editors/zed.js CHANGED
@@ -81,6 +81,44 @@ function queryDb(sql) {
81
81
  }
82
82
  }
83
83
 
84
+ // Zed's `threads` schema varies by version: `worktree_branch` exists in some builds
85
+ // and not others, and older builds lack `created_at`. Naming a column that is not
86
+ // there fails the WHOLE query with "no such column", which cost every Zed thread on
87
+ // this machine — silently, until the adapter started reporting. So ask the table what
88
+ // it has rather than assuming. Cached: the schema cannot change while we scan.
89
+ let threadColumns = null;
90
+ function getThreadColumns() {
91
+ if (threadColumns) return threadColumns;
92
+ if (!fs.existsSync(THREADS_DB)) return new Set();
93
+ try {
94
+ const db = new Database(THREADS_DB, { readonly: true });
95
+ const cols = db.prepare('PRAGMA table_info(threads)').all().map((r) => r.name);
96
+ db.close();
97
+ // PRAGMA on a missing or renamed table SUCCEEDS with zero rows rather than
98
+ // throwing. Left unreported, that would cache an empty set, return no sessions,
99
+ // and look exactly like an unused Zed install — the same silent whole-adapter
100
+ // drop this function exists to end. Do not cache it either: the next scan should
101
+ // look again rather than inherit the verdict.
102
+ if (cols.length === 0) {
103
+ adapterErrors.record('zed', new Error('threads table missing or unreadable — incompatible Zed schema'), THREADS_DB);
104
+ return new Set();
105
+ }
106
+ threadColumns = new Set(cols);
107
+ return threadColumns;
108
+ } catch (e) {
109
+ adapterErrors.record('zed', e, THREADS_DB);
110
+ return new Set();
111
+ }
112
+ }
113
+
114
+ // index.js calls this between passes of a long-running `ship --loop`. Without it the
115
+ // column set would be fixed for the life of the process, so a Zed upgrade mid-run
116
+ // would keep querying a column that no longer exists — or keep ignoring one newly
117
+ // added — until someone restarted the shipper.
118
+ function resetCache() {
119
+ threadColumns = null;
120
+ }
121
+
84
122
  function queryBlob(id) {
85
123
  if (!fs.existsSync(THREADS_DB)) return null;
86
124
  try {
@@ -103,22 +141,33 @@ function queryBlob(id) {
103
141
  const name = 'zed';
104
142
 
105
143
  function getChats() {
106
- const rows = queryDb(
107
- 'SELECT id, summary, updated_at, data_type, length(data) as data_size, parent_id, worktree_branch FROM threads ORDER BY updated_at DESC'
108
- );
144
+ const cols = getThreadColumns();
145
+ if (cols.size === 0) return [];
146
+
147
+ // Only these are relied on. Anything else is taken when the build provides it.
148
+ const select = ['id', 'summary', 'updated_at', 'data_type', 'length(data) as data_size', 'parent_id'];
149
+ const hasBranch = cols.has('worktree_branch');
150
+ const hasCreated = cols.has('created_at');
151
+ if (hasBranch) select.push('worktree_branch');
152
+ if (hasCreated) select.push('created_at');
153
+
154
+ const rows = queryDb(`SELECT ${select.join(', ')} FROM threads ORDER BY updated_at DESC`);
109
155
 
110
156
  return rows.map(row => ({
111
157
  source: 'zed',
112
158
  composerId: row.id,
113
159
  name: row.summary || null,
114
- createdAt: row.updated_at ? new Date(row.updated_at).getTime() : null,
160
+ // created_at when the build has it. Falling back to updated_at is not a
161
+ // creation time — it just keeps the field non-null on builds that lack it.
162
+ createdAt: (hasCreated && row.created_at) ? new Date(row.created_at).getTime()
163
+ : (row.updated_at ? new Date(row.updated_at).getTime() : null),
115
164
  lastUpdatedAt: row.updated_at ? new Date(row.updated_at).getTime() : null,
116
165
  mode: 'thread',
117
166
  folder: null,
118
167
  encrypted: false,
119
168
  bubbleCount: 0,
120
169
  _dataType: row.data_type,
121
- _gitBranch: row.worktree_branch,
170
+ _gitBranch: hasBranch ? row.worktree_branch : null,
122
171
  }));
123
172
  }
124
173
 
@@ -226,4 +275,4 @@ function getMCPServers() {
226
275
  return results;
227
276
  }
228
277
 
229
- module.exports = { name, labels, getChats, getMessages, getMCPServers };
278
+ module.exports = { name, labels, getChats, getMessages, getMCPServers, resetCache };
@@ -17,6 +17,24 @@ the install script that builds the native binding. Without it those five silentl
17
17
  return zero sessions and you ship a partial history. Verify with step 4 —
18
18
  `memhouse discover` names any adapter it had to skip.
19
19
 
20
+ On `EACCES`, decide by **who owns the prefix root**. Not the path — Homebrew's
21
+ prefix is `/usr/local` on Intel and `/opt/homebrew` on Apple Silicon and is
22
+ user-owned in both. Not the failing file either — an earlier `sudo npm` leaves
23
+ root-owned files inside a prefix that is still the user's, and judging by those
24
+ misreads it as system-managed.
25
+
26
+ ```bash
27
+ ls -ld "$(npm prefix -g)" # the prefix ROOT decides
28
+ ```
29
+
30
+ Prefix root owned by `root` → a system-managed Node; re-run as
31
+ `sudo npm install -g memhouse --allow-scripts=better-sqlite3`.
32
+
33
+ Prefix root owned by the current user → the prefix is theirs (Homebrew, fnm, nvm,
34
+ volta), so the root-owned file npm tripped on is a stray from an earlier `sudo npm`
35
+ and another `sudo` adds more. Repair that path instead:
36
+ `sudo chown -R "$(id -u):$(id -g)" <path npm named>` (same for `~/.npm`).
37
+
20
38
  From a checkout instead (contributors): `npm install --no-audit --no-fund` in the
21
39
  repo root — its `allowScripts` field already covers the binding — then substitute
22
40
  `node bin/memhouse.js` for `memhouse` below.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memhouse",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "memhouse — agent conversation memory across 17 editors: parse-on-client shippers, a typed ClickHouse store, kernel-agency install, and the agentlytics dashboard",
5
5
  "main": "bin/memhouse.js",
6
6
  "bin": {