persnally 2.5.0 → 2.5.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
@@ -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`);
@@ -55,13 +55,14 @@
55
55
  /* ── delta strip ── */
56
56
  .delta {
57
57
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
58
- padding: 14px 18px; margin-bottom: 32px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
58
+ padding: 14px 18px; margin-bottom: 32px; display: flex; flex-direction: column; align-items: flex-start; gap: 9px;
59
59
  }
60
- .delta .lead { font-size: 11px; letter-spacing: 1.3px; text-transform: uppercase; color: var(--dim); white-space: nowrap; }
61
- .delta .items { display: flex; gap: 22px; flex-wrap: wrap; flex: 1; }
62
- .delta .di { font-size: 14px; color: var(--dim); }
60
+ .delta .lead { font-size: 11px; letter-spacing: 1.3px; text-transform: uppercase; color: var(--dim); }
61
+ .delta .items { font-size: 14px; line-height: 1.7; color: var(--dim); }
62
+ .delta .di { color: var(--dim); }
63
63
  .delta .di b { color: var(--text); font-weight: 600; }
64
64
  .delta .di .arr { color: var(--text); font-weight: 700; }
65
+ .delta .sep { color: var(--faint); }
65
66
 
66
67
  /* ── hero ── */
67
68
  .hero { margin-bottom: 8px; }
@@ -188,6 +189,8 @@
188
189
  .bar-track { width: 64px; }
189
190
  td { padding: 8px 6px; font-size: 13px; }
190
191
  .del { padding: 3px 7px; }
192
+ .delta .items { display: flex; flex-direction: column; gap: 6px; line-height: 1.45; }
193
+ .delta .sep { display: none; } /* stacked on phones — separators only read inline */
191
194
  }
192
195
  @media (prefers-reduced-motion: reduce) { .reveal { animation: none; opacity: 1; transform: none; } .read.fresh { animation: none; } }
193
196
  </style>
@@ -327,7 +330,7 @@ function renderDelta(topics, reads, assertions) {
327
330
  const na = assertions.filter(a => a.ts > now).length;
328
331
  if (na) items.push(`<span class="di"><b>${na}</b> new ${na>1?"patterns":"pattern"} noticed</span>`);
329
332
  if (!items.length) { el.className = "delta reveal"; el.innerHTML = `<span class="lead">all caught up</span><div class="items"><span class="di">steady since ${timeAgo(now)} — the engine keeps it current while you work.</span></div>`; return; }
330
- el.className = "delta reveal"; el.innerHTML = `<span class="lead">since you last looked</span><div class="items">${items.join("")}</div>`;
333
+ el.className = "delta reveal"; el.innerHTML = `<span class="lead">since you last looked</span><div class="items">${items.join(' <span class="sep">·</span> ')}</div>`;
331
334
  }
332
335
  function saveSnapshot(topics) { const w = {}; topics.forEach(t => w[t.topic_key]=t.weight); localStorage.setItem(SNAP_KEY, JSON.stringify({ t:new Date().toISOString(), weights:w })); }
333
336
 
@@ -434,7 +437,7 @@ function renderEngage(a) {
434
437
  const c = d.reads ? (today ? "var(--text)" : "var(--dim)") : "var(--line-2)";
435
438
  return `<i style="height:${h}px;background:${c}" title="${esc(d.date)}: ${d.reads}"></i>`;
436
439
  }).join("");
437
- const ret = a.retainedWeek2 === null ? `day ${a.daysSinceFirst}/14` : (a.retainedWeek2 ? "active ✓" : "inactive ✗");
440
+ const ret = a.retainedWeek2 === null ? `day ${a.daysSinceFirstRead}/14` : (a.retainedWeek2 ? "active ✓" : "inactive ✗");
438
441
  el.innerHTML = `<span><b>${a.reads7d}</b> this week</span><span>active <b>${a.activeDays14d}</b> of last 14 days</span>` +
439
442
  `<span class="spark" title="context reads, last 14 days">${bars}</span><span class="rk">week-2: ${esc(ret)}</span>`;
440
443
  }
@@ -707,7 +710,7 @@ function DEMO_DATA() {
707
710
  { 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
711
  { 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
712
  ],
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 })) },
713
+ 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
714
  voice: {
712
715
  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
716
  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.2",
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",