persnally 2.5.0 → 2.5.1

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
@@ -93,14 +93,16 @@ persnallyd import claude|claude-code|chatgpt|git <path>
93
93
  persnallyd scope <client> <categories> # limit what a client can read
94
94
  persnallyd profile # synthesize the profile
95
95
  persnallyd consolidate # reflect now: refresh decay, add behavior patterns
96
+ persnallyd voice # refresh your "how you write" fingerprint (offline)
96
97
  persnallyd show [topics|events|profile]
97
- persnallyd forget <topic> | --all | --batch <id>
98
+ persnallyd activity # context-read engagement over time (retention pulse)
99
+ persnallyd forget <topic> | --all | --batch <id> | --style <dim> <pattern>
98
100
  persnallyd config set-key <sk-ant-…> # key for the background daemon
99
101
  ```
100
102
 
101
103
  ## Status
102
104
 
103
- Early and moving fast — see [ROADMAP.md](./ROADMAP.md). Today: import from Claude, ChatGPT, Claude Code, and git; a decay-weighted interest graph; an evidence-linked profile; a local dashboard; per-client permission scoping; nightly consolidation; and the MCP layer that serves it all. Next: cross-tool context everywhere, then a behavior model that can answer *what would I do here?*
105
+ Early and moving fast — see [ROADMAP.md](./ROADMAP.md). Today: import from Claude, ChatGPT, Claude Code, and git; a decay-weighted interest graph; an evidence-linked profile; a voice & convention layer so connected tools answer the way you write; a local dashboard with full provenance and one-click deletion; per-client permission scoping; nightly consolidation; and the MCP layer that serves it all. Next: cross-tool context everywhere, then a behavior model that can answer *what would I do here?*
104
106
 
105
107
  ## License
106
108
 
package/build/src/cli.js CHANGED
@@ -471,7 +471,7 @@ async function main() {
471
471
  console.log("No activity yet — run an import or connect a client.");
472
472
  return;
473
473
  }
474
- const verdict = a.retainedWeek2 === null ? `in progress (day ${a.daysSinceFirst}/14)` : a.retainedWeek2 ? "active ✓" : "inactive ✗";
474
+ const verdict = a.retainedWeek2 === null ? `in progress (day ${a.daysSinceFirstRead}/14 of reads)` : a.retainedWeek2 ? "active ✓" : "inactive ✗";
475
475
  console.log(`Onboarded ${a.daysSinceFirst}d ago · ${a.totalReads} context read(s) total`);
476
476
  console.log(`Reads: ${a.reads7d} this week · ${a.reads30d} this month`);
477
477
  console.log(`Active: ${a.activeDays7d}/7 days · ${a.activeDays14d}/14 days`);
@@ -434,7 +434,7 @@ function renderEngage(a) {
434
434
  const c = d.reads ? (today ? "var(--text)" : "var(--dim)") : "var(--line-2)";
435
435
  return `<i style="height:${h}px;background:${c}" title="${esc(d.date)}: ${d.reads}"></i>`;
436
436
  }).join("");
437
- const ret = a.retainedWeek2 === null ? `day ${a.daysSinceFirst}/14` : (a.retainedWeek2 ? "active ✓" : "inactive ✗");
437
+ const ret = a.retainedWeek2 === null ? `day ${a.daysSinceFirstRead}/14` : (a.retainedWeek2 ? "active ✓" : "inactive ✗");
438
438
  el.innerHTML = `<span><b>${a.reads7d}</b> this week</span><span>active <b>${a.activeDays14d}</b> of last 14 days</span>` +
439
439
  `<span class="spark" title="context reads, last 14 days">${bars}</span><span class="rk">week-2: ${esc(ret)}</span>`;
440
440
  }
@@ -707,7 +707,7 @@ function DEMO_DATA() {
707
707
  { ts:iso(86400000*2), payload:{ claim:"You consistently choose local-first, auditable tools over cloud convenience.", kind:"preference", confidence:0.86 }, provenance:{ kind:"derived" } },
708
708
  { ts:iso(86400000*6), payload:{ claim:"You generate new ideas fastest right when your main bet stalls.", kind:"behavior", confidence:0.71 }, provenance:{ kind:"derived" } },
709
709
  ],
710
- activity: { firstEventAt: iso(86400000*214), lastReadAt: iso(3600000*2), daysSinceFirst: 214, totalReads: 1247, reads7d: 33, reads30d: 142, activeDays7d: 5, activeDays14d: 12, retainedWeek2: true, daily: [2,0,3,5,1,4,6,2,0,3,7,5,8,4].map((r,i)=>({ date:new Date(now-(13-i)*86400000).toISOString().slice(0,10), reads:r })) },
710
+ activity: { firstEventAt: iso(86400000*214), firstReadAt: iso(86400000*210), lastReadAt: iso(3600000*2), daysSinceFirst: 214, daysSinceFirstRead: 210, totalReads: 1247, reads7d: 33, reads30d: 142, activeDays7d: 5, activeDays14d: 12, retainedWeek2: true, daily: [2,0,3,5,1,4,6,2,0,3,7,5,8,4].map((r,i)=>({ date:new Date(now-(13-i)*86400000).toISOString().slice(0,10), reads:r })) },
711
711
  voice: {
712
712
  pack: "Write like this user: terse — short, declarative sentences; leads with imperatives, minimal preamble; states things flatly; no emoji; casual register (lowercases “i”); recurring phrasing: “be 100% sure”, “industry best practices”, “validate whether it's valid or not”.",
713
713
  items: [
@@ -37,8 +37,10 @@ export interface StoredProfile {
37
37
  }
38
38
  export interface Activity {
39
39
  firstEventAt: string | null;
40
+ firstReadAt: string | null;
40
41
  lastReadAt: string | null;
41
42
  daysSinceFirst: number;
43
+ daysSinceFirstRead: number;
42
44
  totalReads: number;
43
45
  reads7d: number;
44
46
  reads30d: number;
@@ -161,6 +161,11 @@ export class EventStore {
161
161
  const reads = this.query({ type: "context.read", limit: 1_000_000 }); // ts DESC
162
162
  const firstEventAt = this.db.prepare("SELECT MIN(ts) m FROM events").get().m;
163
163
  const firstMs = firstEventAt ? new Date(firstEventAt).getTime() : null;
164
+ // Retention is anchored to when serving actually began (the first read), not
165
+ // onboarding — so a gap between setup and the first read can't read as a
166
+ // false "not retained". For a fresh install the two are minutes apart.
167
+ const firstReadAt = reads.length ? reads[reads.length - 1].ts : null;
168
+ const firstReadMs = firstReadAt ? new Date(firstReadAt).getTime() : null;
164
169
  const daily = new Map();
165
170
  for (let i = 13; i >= 0; i--)
166
171
  daily.set(dayKey(now - i * DAY), 0);
@@ -181,19 +186,21 @@ export class EventStore {
181
186
  reads30d++;
182
187
  if (daily.has(k))
183
188
  daily.set(k, (daily.get(k) ?? 0) + 1);
184
- if (firstMs !== null && t >= firstMs + 7 * DAY && t < firstMs + 14 * DAY)
189
+ if (firstReadMs !== null && t >= firstReadMs + 7 * DAY && t < firstReadMs + 14 * DAY)
185
190
  week2Read = true;
186
191
  }
187
192
  return {
188
193
  firstEventAt,
194
+ firstReadAt,
189
195
  lastReadAt: reads.length ? reads[0].ts : null,
190
196
  daysSinceFirst: firstMs !== null ? Math.max(0, Math.floor((now - firstMs) / DAY)) : 0,
197
+ daysSinceFirstRead: firstReadMs !== null ? Math.max(0, Math.floor((now - firstReadMs) / DAY)) : 0,
191
198
  totalReads: reads.length,
192
199
  reads7d,
193
200
  reads30d,
194
201
  activeDays7d: days7.size,
195
202
  activeDays14d: days14.size,
196
- retainedWeek2: firstMs !== null && now >= firstMs + 14 * DAY ? week2Read : null,
203
+ retainedWeek2: firstReadMs !== null && now >= firstReadMs + 14 * DAY ? week2Read : null,
197
204
  daily: [...daily.entries()].map(([date, r]) => ({ date, reads: r })),
198
205
  };
199
206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "persnally",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "Your own context engine — local-first, across every AI. So every AI finally knows you.",
6
6
  "type": "module",