phaedo-mcp 0.3.3 → 0.3.4

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/capture-local.js CHANGED
@@ -53,6 +53,23 @@ async function localSnapshots(now) {
53
53
  return out;
54
54
  }
55
55
 
56
+ // How many captured sessions the local state holds, across every surface: the live ones plus
57
+ // those evicted into the baseline (their counts are still in it). For the starter-profile
58
+ // readout only; 0 on any failure — it describes, it never gates.
59
+ export async function localCaptureSessionCount() {
60
+ try {
61
+ const stateMod = await loadCaptureModule('capture/state.js');
62
+ const state = stateMod.loadState(stateMod.defaultStatePath());
63
+ let n = 0;
64
+ for (const s of Object.values(state.surfaces || {})) {
65
+ n += Object.keys((s && s.sessions) || {}).length + (Array.isArray(s && s.evicted) ? s.evicted.length : 0);
66
+ }
67
+ return n;
68
+ } catch {
69
+ return 0;
70
+ }
71
+ }
72
+
56
73
  // Fold the local capture state into a vault payload's fingerprint and return the
57
74
  // folded payload (or the input unchanged when there is nothing to fold / the
58
75
  // modules are unavailable). Pure with respect to the input object.
@@ -15,7 +15,7 @@ import { resolve, dirname } from 'path';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { hasPairingRecord, loadPairingRecord, loadFingerprintFromPhone, PhoneError } from './phone-source.js';
17
17
  import { defaultStateDir, writeCache, readCache, clearCache } from './cache.js';
18
- import { foldLocalCapture, isLocalCaptureOwner } from './capture-local.js';
18
+ import { foldLocalCapture, isLocalCaptureOwner, localCaptureSessionCount } from './capture-local.js';
19
19
  import { Capabilities, AuthoredSource } from './lib/phaedo-crypto.js';
20
20
  import { PhaedoError } from './errors.js';
21
21
 
@@ -137,6 +137,33 @@ async function loadStarterProfile(now = new Date().toISOString()) {
137
137
  return markStarter(foldConfigHome(normalizeIdentity(vd)));
138
138
  }
139
139
 
140
+ // HOW FAR ALONG a starter profile is, in words a person can act on. "Learned" counts distinct
141
+ // traits (layer + field), not observations: ten corrections about length are one thing learned.
142
+ // The stages are about what to do next, which is the point of saying it at all: early on, keep
143
+ // working; once it is a solid start, the phone app is what adds the rest (its interview and the
144
+ // decisions the subject approves, which sessions cannot show). Cut-offs are deliberately coarse
145
+ // and are not a confidence measure — the projection's own confidence section is that.
146
+ export const STARTER_STAGES = [
147
+ { min: 8, text: 'a solid start. The Phaedo phone app adds what sessions can\'t show (https://phaedo.so).' },
148
+ { min: 3, text: 'taking shape; it keeps learning as you work.' },
149
+ { min: 0, text: 'just getting started; it fills in as you work.' },
150
+ ];
151
+
152
+ export async function summarizeStarter(vd) {
153
+ const layers = (vd && vd.phaedo_fingerprint && vd.phaedo_fingerprint.layers) || {};
154
+ const traits = new Set();
155
+ let areas = 0;
156
+ for (const [id, layer] of Object.entries(layers)) {
157
+ const sigs = (layer && Array.isArray(layer.signals)) ? layer.signals : [];
158
+ if (sigs.length) areas++;
159
+ for (const sg of sigs) if (sg && sg.field) traits.add(`${id}.${sg.field}`);
160
+ }
161
+ const learned = traits.size;
162
+ const sessions = await localCaptureSessionCount();
163
+ const stage = STARTER_STAGES.find((st) => learned >= st.min).text;
164
+ return { learned, areas, sessions, stage };
165
+ }
166
+
140
167
  /** One line for the server log describing where the profile will come from, or null when there
141
168
  * is nothing a person needs told. Never touches the phone: a pull at startup would put an
142
169
  * "authorize" card on it with nobody asking. */
@@ -146,7 +173,13 @@ export async function profileStatusLine(baseDir) {
146
173
  if (env) return null;
147
174
  if (hasPairingRecord(baseDir)) return null;
148
175
  if (existsSync(resolve(baseDir, 'fingerprint.json'))) return null;
149
- if (await loadStarterProfile()) return 'no phone paired; using a starter profile built from your own sessions on this computer.';
176
+ const starter = await loadStarterProfile();
177
+ if (starter) {
178
+ const { learned, areas, sessions, stage } = await summarizeStarter(starter);
179
+ return `no phone paired; using a starter profile from your own sessions on this computer: ` +
180
+ `${learned} thing${learned === 1 ? '' : 's'} learned across ${areas} area${areas === 1 ? '' : 's'}` +
181
+ `${sessions ? ` from ${sessions} session${sessions === 1 ? '' : 's'}` : ''} — ${stage}`;
182
+ }
150
183
  return NO_PROFILE_LOG;
151
184
  }
152
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phaedo-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/pull.js CHANGED
@@ -7,12 +7,12 @@
7
7
 
8
8
  import { fileURLToPath } from 'url';
9
9
  import { dirname } from 'path';
10
- import { loadFingerprint, sourceOf } from './fingerprint-source.js';
10
+ import { loadFingerprint, sourceOf, summarizeStarter } from './fingerprint-source.js';
11
11
 
12
12
  const BASE_DIR = dirname(fileURLToPath(import.meta.url));
13
13
 
14
14
  loadFingerprint(BASE_DIR)
15
- .then((vd) => {
15
+ .then(async (vd) => {
16
16
  const fp = (vd && vd.phaedo_fingerprint) || {};
17
17
  const layers = fp.layers ? Object.keys(fp.layers).length : 0;
18
18
  const src = sourceOf(vd);
@@ -22,8 +22,10 @@ loadFingerprint(BASE_DIR)
22
22
  // whole point of this command is to verify the pairing works, so it has to distinguish
23
23
  // "your phone answered" from "your phone didn't, here's an old copy".
24
24
  if (src.starter) {
25
+ const { learned, areas, sessions, stage } = await summarizeStarter(vd);
25
26
  process.stdout.write(
26
- `✓ No phone paired — serving a starter profile built from your own sessions on this computer, ${layers} layer(s).\n` +
27
+ `✓ No phone paired — serving a starter profile from your own sessions on this computer:\n` +
28
+ ` ${learned} thing(s) learned across ${areas} area(s)${sessions ? ` from ${sessions} session(s)` : ''} — ${stage}\n` +
27
29
  ` Pair the Phaedo phone app (npm run pair) to replace it with your full profile.\n`
28
30
  );
29
31
  process.exit(0);
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.phaedo-labs/phaedo-mcp",
4
4
  "description": "Make AI apps answer your way, check with you before acting, and get approvals on your phone.",
5
- "version": "0.3.3",
5
+ "version": "0.3.4",
6
6
  "repository": {
7
7
  "url": "https://github.com/phaedo-labs/phaedo-mcp",
8
8
  "source": "github",
@@ -13,7 +13,7 @@
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "phaedo-mcp",
16
- "version": "0.3.3",
16
+ "version": "0.3.4",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }