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 +17 -0
- package/fingerprint-source.js +35 -2
- package/package.json +1 -1
- package/pull.js +5 -3
- package/server.json +2 -2
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.
|
package/fingerprint-source.js
CHANGED
|
@@ -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
|
-
|
|
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
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
|
|
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.
|
|
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.
|
|
16
|
+
"version": "0.3.4",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
}
|