@somacheck/vibecheck 0.3.0 → 0.5.0
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 +20 -15
- package/dist/api.js +121 -63
- package/dist/cli.js +15 -9
- package/dist/client-setup.js +1 -1
- package/dist/config.js +46 -5
- package/dist/constants.js +4 -3
- package/dist/link.js +1 -1
- package/dist/readiness.js +4 -3
- package/dist/server.js +50 -2
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@ Your agent has your context. It does not have your gut. This is the bridge.
|
|
|
9
9
|
|
|
10
10
|
Fair. Here is what you need, without executing anything.
|
|
11
11
|
|
|
12
|
-
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.
|
|
12
|
+
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.5.0 link <CODE> --client <CLIENT>`
|
|
13
13
|
exchanges a short-lived pairing code, generated inside the SomaCheck iOS app, for
|
|
14
14
|
a bearer token. It writes that token to `~/.sensie/config.json` with mode `0600`,
|
|
15
15
|
offers to configure supported agent clients found on the computer, and checks the
|
|
16
16
|
connection without printing the token. Setup is not reported ready until the
|
|
17
17
|
selected client has the exact pinned `vibecheck` MCP registration, the backend
|
|
18
|
-
accepts protocol `
|
|
18
|
+
accepts protocol `4` / toolset `vibecheck-0.5`, and both status and context
|
|
19
19
|
probes succeed. It contacts exactly one network host: the Supabase project below.
|
|
20
20
|
|
|
21
21
|
**It will refuse unsafe sandbox linking, on purpose.** `link` checks safety
|
|
@@ -32,13 +32,17 @@ and that its persistent MCP config can be inspected and written. If that preflig
|
|
|
32
32
|
fails, the pairing code is not used. Non-interactive implicit selection,
|
|
33
33
|
`--client all`, and `--client none` are rejected before redemption.
|
|
34
34
|
|
|
35
|
-
**What the server does once running.** It exposes MCP tools that
|
|
36
|
-
statement for the user to test,
|
|
37
|
-
gesture context, and keep a
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
**What is sent.** A
|
|
41
|
-
|
|
35
|
+
**What the server does once running.** It exposes MCP tools that share bounded,
|
|
36
|
+
user-authorized context summaries, create a statement for the user to test,
|
|
37
|
+
read its outcome, recover recent completed gesture context, and keep a
|
|
38
|
+
three-statement feed stocked.
|
|
39
|
+
|
|
40
|
+
**What is sent.** A statement string, bounded derived context observations the
|
|
41
|
+
agent explicitly shares, and a bearer token identifying the link. Tool
|
|
42
|
+
instructions forbid raw conversations, photos, identifiers, and diagnostic
|
|
43
|
+
claims; the backend enforces the bounded capsule shape and rejects
|
|
44
|
+
secret-shaped content. **What is never sent: raw sensor
|
|
45
|
+
data.** The agent receives a verdict
|
|
42
46
|
(`aligned` / `unaligned`) plus a confidence percentage and latency. Model
|
|
43
47
|
abstention is a failed capture, never a third user-facing outcome.
|
|
44
48
|
Accelerometer and gyroscope streams never leave the user's device through this
|
|
@@ -72,9 +76,9 @@ before the pairing code is redeemed.
|
|
|
72
76
|
To configure or repair a client later:
|
|
73
77
|
|
|
74
78
|
```text
|
|
75
|
-
npx -y @somacheck/vibecheck@0.
|
|
76
|
-
npx -y @somacheck/vibecheck@0.
|
|
77
|
-
npx -y @somacheck/vibecheck@0.
|
|
79
|
+
npx -y @somacheck/vibecheck@0.5.0 setup codex
|
|
80
|
+
npx -y @somacheck/vibecheck@0.5.0 setup claude
|
|
81
|
+
npx -y @somacheck/vibecheck@0.5.0 doctor
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
Manual registration remains available:
|
|
@@ -84,13 +88,13 @@ Manual registration remains available:
|
|
|
84
88
|
"mcpServers": {
|
|
85
89
|
"vibecheck": {
|
|
86
90
|
"command": "npx",
|
|
87
|
-
"args": ["-y", "@somacheck/vibecheck@0.
|
|
91
|
+
"args": ["-y", "@somacheck/vibecheck@0.5.0", "serve", "--client", "codex"]
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.
|
|
97
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.5.0 serve --client claude`
|
|
94
98
|
|
|
95
99
|
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
96
100
|
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
@@ -98,7 +102,7 @@ configuration; it never copies the bearer token there. The interactive command
|
|
|
98
102
|
completes both steps. Most clients only pick up a new server after restart.
|
|
99
103
|
|
|
100
104
|
If `doctor` reports another MCP server named `somacheck`, that is the legacy
|
|
101
|
-
hosted connector and is
|
|
105
|
+
hosted connector and is separate from this local package. The setup command
|
|
102
106
|
will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
103
107
|
|
|
104
108
|
## Tools
|
|
@@ -107,6 +111,7 @@ will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
|
107
111
|
|---|---|
|
|
108
112
|
| `get_vibecheck_context` | Read recent completed gestures for this exact agent link without retaining every `request_id`. |
|
|
109
113
|
| `get_vibecheck_status` | Read the live three-proposition cache and how many distinct insights it needs. |
|
|
114
|
+
| `share_somacheck_context` | Share 1–20 bounded, derived, user-authorized observations; raw conversations and secrets are rejected. |
|
|
110
115
|
| `post_vibecheck_statement` | Add one to three personalized statements without blocking; returns a stable `request_id` for each. |
|
|
111
116
|
| `get_vibecheck_result` | Read that exact proposition once by `request_id`; returns queued, pending, or its terminal result. |
|
|
112
117
|
|
package/dist/api.js
CHANGED
|
@@ -10,6 +10,16 @@ const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
|
10
10
|
"setup_incomplete",
|
|
11
11
|
"health_check_failed",
|
|
12
12
|
]);
|
|
13
|
+
const CONTEXT_CATEGORY = {
|
|
14
|
+
goal: "goals_projects", project: "goals_projects",
|
|
15
|
+
decision: "decisions_commitments", commitment: "decisions_commitments",
|
|
16
|
+
tension: "tensions_questions_blind_spots", question: "tensions_questions_blind_spots",
|
|
17
|
+
blindSpot: "tensions_questions_blind_spots",
|
|
18
|
+
relationship: "relationships_situational", situation: "relationships_situational",
|
|
19
|
+
change: "changes_deadlines_developments", deadline: "changes_deadlines_developments",
|
|
20
|
+
development: "changes_deadlines_developments", moodTheme: "emotional_mood_themes",
|
|
21
|
+
preference: "corrections_dismissals_saves_preferences",
|
|
22
|
+
};
|
|
13
23
|
export class SomaCheckHttpError extends Error {
|
|
14
24
|
status;
|
|
15
25
|
constructor(status) {
|
|
@@ -128,6 +138,75 @@ function parseStatus(row) {
|
|
|
128
138
|
},
|
|
129
139
|
};
|
|
130
140
|
}
|
|
141
|
+
/** Shared strict decoders used by both local stdio and hosted OAuth MCP. */
|
|
142
|
+
export function decodeVibecheckStatus(value) {
|
|
143
|
+
return parseStatus(asObject(value));
|
|
144
|
+
}
|
|
145
|
+
export function decodeCreatedRequests(value) {
|
|
146
|
+
return allRows(value).map((row) => {
|
|
147
|
+
const presentationState = requiredString(row, "presentation_state");
|
|
148
|
+
if (presentationState !== "presented" && presentationState !== "queued") {
|
|
149
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
request_id: requiredString(row, "request_id"),
|
|
153
|
+
presentation_state: presentationState,
|
|
154
|
+
stale_at: nullableString(row, "expires_at"),
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export function decodePollResponse(value) {
|
|
159
|
+
const row = asObject(value);
|
|
160
|
+
const status = row.status;
|
|
161
|
+
if (typeof status !== "string" || !STATUSES.has(status)) {
|
|
162
|
+
throw new Error("SomaCheck returned an invalid request status.");
|
|
163
|
+
}
|
|
164
|
+
const verdict = row.verdict;
|
|
165
|
+
const confidence = row.confidence;
|
|
166
|
+
const latency = row.latency_s;
|
|
167
|
+
if (verdict !== null && (typeof verdict !== "string" || !VERDICTS.has(verdict))) {
|
|
168
|
+
throw new Error("SomaCheck returned an invalid verdict.");
|
|
169
|
+
}
|
|
170
|
+
if (confidence !== null && (typeof confidence !== "number" || !Number.isFinite(confidence))) {
|
|
171
|
+
throw new Error("SomaCheck returned an invalid confidence.");
|
|
172
|
+
}
|
|
173
|
+
if (latency !== null && (typeof latency !== "number" || !Number.isFinite(latency) || latency < 0)) {
|
|
174
|
+
throw new Error("SomaCheck returned an invalid latency.");
|
|
175
|
+
}
|
|
176
|
+
if (status === "answered") {
|
|
177
|
+
if (verdict === null || confidence === null || confidence < 0 || confidence > 1 || latency === null) {
|
|
178
|
+
throw new Error("SomaCheck returned an incomplete answered result.");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (verdict !== null || confidence !== null || latency !== null) {
|
|
182
|
+
throw new Error("SomaCheck returned an inconsistent pending result.");
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
status: status,
|
|
186
|
+
verdict: verdict,
|
|
187
|
+
confidence: confidence,
|
|
188
|
+
latency_s: latency,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export function decodeVibecheckContext(value) {
|
|
192
|
+
return allRows(value).map((row) => {
|
|
193
|
+
const verdict = requiredString(row, "verdict");
|
|
194
|
+
const confidence = requiredNumber(row, "confidence");
|
|
195
|
+
if (!VERDICTS.has(verdict)) {
|
|
196
|
+
throw new Error("SomaCheck returned an invalid verdict.");
|
|
197
|
+
}
|
|
198
|
+
if (confidence < 0 || confidence > 1) {
|
|
199
|
+
throw new Error("SomaCheck returned an invalid confidence.");
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
request_id: requiredString(row, "request_id"),
|
|
203
|
+
statement: requiredString(row, "statement"),
|
|
204
|
+
verdict: verdict,
|
|
205
|
+
confidence,
|
|
206
|
+
answered_at: requiredTimestamp(row, "answered_at"),
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
}
|
|
131
210
|
function parseHandshake(row) {
|
|
132
211
|
const readinessStatus = requiredString(row, "readiness_status");
|
|
133
212
|
if (!HANDSHAKE_READINESS_STATUSES.has(readinessStatus)) {
|
|
@@ -151,6 +230,24 @@ function parseHandshake(row) {
|
|
|
151
230
|
}
|
|
152
231
|
return result;
|
|
153
232
|
}
|
|
233
|
+
export function decodeAgentContextShare(value) {
|
|
234
|
+
const row = firstRow(value);
|
|
235
|
+
const state = requiredString(row, "state");
|
|
236
|
+
if (state !== "ready")
|
|
237
|
+
throw new Error("SomaCheck returned an invalid context state.");
|
|
238
|
+
const observationCount = requiredInteger(row, "observation_count");
|
|
239
|
+
if (observationCount < 1 || observationCount > 20) {
|
|
240
|
+
throw new Error("SomaCheck returned an invalid observation count.");
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
source_id: requiredString(row, "source_id"),
|
|
244
|
+
entry_id: requiredString(row, "entry_id"),
|
|
245
|
+
accepted: requiredBoolean(row, "accepted"),
|
|
246
|
+
state,
|
|
247
|
+
observation_count: observationCount,
|
|
248
|
+
captured_at: requiredTimestamp(row, "captured_at"),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
154
251
|
export class SupabaseAgentApi {
|
|
155
252
|
#endpoint;
|
|
156
253
|
#apiKey;
|
|
@@ -165,77 +262,38 @@ export class SupabaseAgentApi {
|
|
|
165
262
|
return requiredString(row, "token");
|
|
166
263
|
}
|
|
167
264
|
async createRequests(token, statements) {
|
|
168
|
-
|
|
169
|
-
return rows.map((row) => {
|
|
170
|
-
const presentationState = requiredString(row, "presentation_state");
|
|
171
|
-
if (presentationState !== "presented" && presentationState !== "queued") {
|
|
172
|
-
throw new Error("SomaCheck returned an incomplete response.");
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
request_id: requiredString(row, "request_id"),
|
|
176
|
-
presentation_state: presentationState,
|
|
177
|
-
stale_at: nullableString(row, "expires_at"),
|
|
178
|
-
};
|
|
179
|
-
});
|
|
265
|
+
return decodeCreatedRequests(await this.#rpcJson("agent_proposition_batch_create", { token, statements }));
|
|
180
266
|
}
|
|
181
267
|
async pollRequest(token, requestId) {
|
|
182
|
-
|
|
183
|
-
const status = row.status;
|
|
184
|
-
if (typeof status !== "string" || !STATUSES.has(status)) {
|
|
185
|
-
throw new Error("SomaCheck returned an invalid request status.");
|
|
186
|
-
}
|
|
187
|
-
const verdict = row.verdict;
|
|
188
|
-
const confidence = row.confidence;
|
|
189
|
-
const latency = row.latency_s;
|
|
190
|
-
if (verdict !== null && (typeof verdict !== "string" || !VERDICTS.has(verdict))) {
|
|
191
|
-
throw new Error("SomaCheck returned an invalid verdict.");
|
|
192
|
-
}
|
|
193
|
-
if (confidence !== null && (typeof confidence !== "number" || !Number.isFinite(confidence))) {
|
|
194
|
-
throw new Error("SomaCheck returned an invalid confidence.");
|
|
195
|
-
}
|
|
196
|
-
if (latency !== null && (typeof latency !== "number" || !Number.isFinite(latency) || latency < 0)) {
|
|
197
|
-
throw new Error("SomaCheck returned an invalid latency.");
|
|
198
|
-
}
|
|
199
|
-
if (status === "answered") {
|
|
200
|
-
if (verdict === null || confidence === null || confidence < 0 || confidence > 1 || latency === null) {
|
|
201
|
-
throw new Error("SomaCheck returned an incomplete answered result.");
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
else if (verdict !== null || confidence !== null || latency !== null) {
|
|
205
|
-
throw new Error("SomaCheck returned an inconsistent pending result.");
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
status: status,
|
|
209
|
-
verdict: verdict,
|
|
210
|
-
confidence: confidence,
|
|
211
|
-
latency_s: latency,
|
|
212
|
-
};
|
|
268
|
+
return decodePollResponse(await this.#rpc("agent_proposition_result", { token, proposition_id: requestId }));
|
|
213
269
|
}
|
|
214
270
|
async statusRequest(token) {
|
|
215
|
-
return
|
|
271
|
+
return decodeVibecheckStatus(await this.#rpc("agent_proposition_cache_status", { token }));
|
|
216
272
|
}
|
|
217
273
|
async contextRequest(token, limit = 20) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
274
|
+
return decodeVibecheckContext(await this.#rpcJson("agent_proposition_context", { token, p_limit: limit }));
|
|
275
|
+
}
|
|
276
|
+
async shareContext(token, observations) {
|
|
277
|
+
const capturedAt = new Date().toISOString();
|
|
278
|
+
const payload = {};
|
|
279
|
+
for (const observation of observations) {
|
|
280
|
+
const category = CONTEXT_CATEGORY[observation.type];
|
|
281
|
+
(payload[category] ??= []).push({
|
|
282
|
+
type: observation.type,
|
|
283
|
+
summary: observation.summary.trim(),
|
|
284
|
+
confidence: observation.confidence,
|
|
285
|
+
evidence_count: observation.evidence_count,
|
|
286
|
+
captured_at: capturedAt,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return decodeAgentContextShare(await this.#rpcJson("agent_context_capsule_share", {
|
|
290
|
+
token,
|
|
291
|
+
p_payload: payload,
|
|
292
|
+
p_confidence: observations.reduce((sum, item) => sum + item.confidence, 0) / observations.length,
|
|
293
|
+
}));
|
|
236
294
|
}
|
|
237
295
|
async clientHandshake(token, input) {
|
|
238
|
-
const row = await this.#rpc("
|
|
296
|
+
const row = await this.#rpc("agent_client_handshake_v2", {
|
|
239
297
|
token,
|
|
240
298
|
p_client_key: input.client_key,
|
|
241
299
|
p_client_label: input.client_label,
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
5
5
|
import { SupabaseAgentApi } from "./api.js";
|
|
6
6
|
import { clientDisplayName, detectInstalledClients, detectLegacyHostedRegistration, LocalCommandRunner, manualLegacyHostedRemoveCommand, manualRemoveCommand, manualSetupCommand, parseClientChoice, preflightClientPersistence, registerClient, singleNonInteractiveClientSelection, } from "./client-setup.js";
|
|
7
7
|
import { readConfig } from "./config.js";
|
|
8
|
-
import { SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL } from "./constants.js";
|
|
8
|
+
import { PACKAGE_SPEC, SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL } from "./constants.js";
|
|
9
9
|
import { LinkPersistenceError, NonInteractiveLinkError, linkAgent } from "./link.js";
|
|
10
10
|
import { checkReadiness } from "./readiness.js";
|
|
11
11
|
import { createVibecheckServer } from "./server.js";
|
|
@@ -100,6 +100,13 @@ async function preflightLinkClients(clients, interactive) {
|
|
|
100
100
|
const selectedClients = interactive
|
|
101
101
|
? clients === "prompt" ? [] : clients
|
|
102
102
|
: nonInteractiveClient === null ? [] : [nonInteractiveClient];
|
|
103
|
+
if (selectedClients.length > 1) {
|
|
104
|
+
throw new ClientPreflightError([
|
|
105
|
+
"Not linking yet: each local agent needs its own independently revocable pairing code.",
|
|
106
|
+
"Link Claude and Codex separately so one credential is never shared between them.",
|
|
107
|
+
"The pairing code has NOT been used.",
|
|
108
|
+
].join("\n"));
|
|
109
|
+
}
|
|
103
110
|
if (selectedClients.length === 0) {
|
|
104
111
|
throw new ClientPreflightError([
|
|
105
112
|
interactive
|
|
@@ -108,8 +115,8 @@ async function preflightLinkClients(clients, interactive) {
|
|
|
108
115
|
"",
|
|
109
116
|
"Run the command in Terminal, or paste it into the local agent with exactly one",
|
|
110
117
|
"client selection, for example:",
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
` npx -y ${PACKAGE_SPEC} link <CODE> --client claude`,
|
|
119
|
+
` npx -y ${PACKAGE_SPEC} link <CODE> --client codex`,
|
|
113
120
|
"",
|
|
114
121
|
"The pairing code has NOT been used.",
|
|
115
122
|
].join("\n"));
|
|
@@ -142,7 +149,7 @@ async function preflightLinkClients(clients, interactive) {
|
|
|
142
149
|
? `${clientDisplayName(client)} has an existing "vibecheck" MCP entry with a different command or version.`
|
|
143
150
|
: `${clientDisplayName(client)} configuration could not be inspected and written.`;
|
|
144
151
|
const repair = preflight.status === "needs_update"
|
|
145
|
-
? ["", `Remove the stale entry: ${manualRemoveCommand(client)}`, `Then configure
|
|
152
|
+
? ["", `Remove the stale entry: ${manualRemoveCommand(client)}`, `Then configure the current version: ${manualSetupCommand(client)}`]
|
|
146
153
|
: [];
|
|
147
154
|
throw new ClientPreflightError([
|
|
148
155
|
`Not linking here: ${reason}`,
|
|
@@ -160,7 +167,7 @@ async function runDoctor() {
|
|
|
160
167
|
return readiness.ready;
|
|
161
168
|
}
|
|
162
169
|
async function refreshRuntimeHealth(client) {
|
|
163
|
-
const token = (await readConfig(homedir())).token;
|
|
170
|
+
const token = (await readConfig(homedir(), client)).token;
|
|
164
171
|
await api.statusRequest(token);
|
|
165
172
|
await api.contextRequest(token);
|
|
166
173
|
await api.clientHandshake(token, {
|
|
@@ -174,7 +181,7 @@ async function refreshRuntimeHealth(client) {
|
|
|
174
181
|
async function startServer(runtimeClient) {
|
|
175
182
|
const server = createVibecheckServer({
|
|
176
183
|
api,
|
|
177
|
-
loadToken: async () => (await readConfig(homedir())).token,
|
|
184
|
+
loadToken: async () => (await readConfig(homedir(), runtimeClient ?? undefined)).token,
|
|
178
185
|
});
|
|
179
186
|
await server.connect(new StdioServerTransport());
|
|
180
187
|
if (runtimeClient !== null) {
|
|
@@ -196,15 +203,14 @@ async function main() {
|
|
|
196
203
|
? await promptForClients(await detectInstalledClients(runner))
|
|
197
204
|
: requestedClients
|
|
198
205
|
: requestedClients;
|
|
206
|
+
const selectedClients = await preflightLinkClients(requestedOrPromptedClients, interactive);
|
|
199
207
|
await linkAgent(args[1] ?? "", {
|
|
200
208
|
home: homedir(),
|
|
201
209
|
redeem: (code) => api.redeemLink(code),
|
|
202
210
|
output,
|
|
203
211
|
isInteractive: () => interactive,
|
|
204
212
|
allowNonInteractive: !interactive || process.env.SOMACHECK_ALLOW_NON_INTERACTIVE === "1",
|
|
205
|
-
|
|
206
|
-
await preflightLinkClients(requestedOrPromptedClients, interactive);
|
|
207
|
-
},
|
|
213
|
+
client: selectedClients[0],
|
|
208
214
|
});
|
|
209
215
|
return (await runDoctor()) ? 0 : 2;
|
|
210
216
|
}
|
package/dist/client-setup.js
CHANGED
|
@@ -124,7 +124,7 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
124
124
|
return false;
|
|
125
125
|
const packageArgs = args.filter((arg) => arg.startsWith("@somacheck/vibecheck@"));
|
|
126
126
|
return packageArgs.length === 1
|
|
127
|
-
&& /^@somacheck\/vibecheck@0\.[0-
|
|
127
|
+
&& /^@somacheck\/vibecheck@0\.[0-3]\.\d+$/.test(packageArgs[0])
|
|
128
128
|
&& args.every((arg) => arg === "-y" || arg === "--yes" || arg === packageArgs[0]);
|
|
129
129
|
}
|
|
130
130
|
export async function isClientRegistered(client, runner) {
|
package/dist/config.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function preflightConfigPersistence(home) {
|
|
|
20
20
|
await rm(renamedProbe, { force: true });
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export async function readConfig(home) {
|
|
23
|
+
export async function readConfig(home, client) {
|
|
24
24
|
let value;
|
|
25
25
|
try {
|
|
26
26
|
value = JSON.parse(await readFile(configPath(home), "utf8"));
|
|
@@ -28,23 +28,41 @@ export async function readConfig(home) {
|
|
|
28
28
|
catch {
|
|
29
29
|
throw new Error("SomaCheck is not linked. Run: npx -y @somacheck/vibecheck link <CODE>");
|
|
30
30
|
}
|
|
31
|
-
if (value === null || typeof value !== "object"
|
|
31
|
+
if (value === null || typeof value !== "object") {
|
|
32
32
|
throw new Error("SomaCheck link configuration is invalid.");
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
// 0.3.0 legacy config. Keep reading it so existing installations do not
|
|
35
|
+
// break, but every new link is written in the client-scoped v2 shape.
|
|
36
|
+
if ("token" in value) {
|
|
37
|
+
return validatedToken(value.token);
|
|
38
|
+
}
|
|
39
|
+
const stored = value;
|
|
40
|
+
if (stored.version !== 2 || stored.connections === null || typeof stored.connections !== "object") {
|
|
41
|
+
throw new Error("SomaCheck link configuration is invalid.");
|
|
42
|
+
}
|
|
43
|
+
const selected = client ?? stored.default_client;
|
|
44
|
+
if (selected !== "codex" && selected !== "claude") {
|
|
45
|
+
throw new Error("SomaCheck link configuration is invalid.");
|
|
46
|
+
}
|
|
47
|
+
return validatedToken(stored.connections[selected]?.token);
|
|
48
|
+
}
|
|
49
|
+
function validatedToken(token) {
|
|
35
50
|
if (typeof token !== "string" || token.length === 0) {
|
|
36
51
|
throw new Error("SomaCheck link configuration is invalid.");
|
|
37
52
|
}
|
|
38
53
|
return { token };
|
|
39
54
|
}
|
|
40
|
-
export async function writeConfig(home, config) {
|
|
55
|
+
export async function writeConfig(home, config, client) {
|
|
41
56
|
const directory = join(home, ".sensie");
|
|
42
57
|
const destination = configPath(home);
|
|
43
58
|
const temporary = join(directory, `.config.json.${process.pid}.${Date.now()}.tmp`);
|
|
44
59
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
45
60
|
await chmod(directory, 0o700);
|
|
46
61
|
try {
|
|
47
|
-
|
|
62
|
+
const stored = client === undefined
|
|
63
|
+
? config
|
|
64
|
+
: await mergedClientConfig(home, client, config);
|
|
65
|
+
await writeFile(temporary, `${JSON.stringify(stored, null, 2)}\n`, {
|
|
48
66
|
encoding: "utf8",
|
|
49
67
|
flag: "wx",
|
|
50
68
|
mode: 0o600,
|
|
@@ -56,4 +74,27 @@ export async function writeConfig(home, config) {
|
|
|
56
74
|
await rm(temporary, { force: true });
|
|
57
75
|
}
|
|
58
76
|
}
|
|
77
|
+
async function mergedClientConfig(home, client, config) {
|
|
78
|
+
let connections = {};
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(await readFile(configPath(home), "utf8"));
|
|
81
|
+
if (parsed !== null && typeof parsed === "object" && "version" in parsed && parsed.version === 2
|
|
82
|
+
&& "connections" in parsed && parsed.connections !== null && typeof parsed.connections === "object") {
|
|
83
|
+
const existing = parsed.connections;
|
|
84
|
+
for (const key of ["codex", "claude"]) {
|
|
85
|
+
const candidate = existing[key]?.token;
|
|
86
|
+
if (typeof candidate === "string" && candidate.length > 0) {
|
|
87
|
+
connections[key] = { token: candidate };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Missing or legacy config is intentionally migrated by the new link.
|
|
94
|
+
// A legacy token cannot be attributed safely to a client, so it remains
|
|
95
|
+
// readable until the first scoped link and is then replaced.
|
|
96
|
+
}
|
|
97
|
+
connections = { ...connections, [client]: config };
|
|
98
|
+
return { version: 2, default_client: client, connections };
|
|
99
|
+
}
|
|
59
100
|
//# sourceMappingURL=config.js.map
|
package/dist/constants.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
export const SUPABASE_URL = "https://pbldcmniommltbdwuykk.supabase.co";
|
|
3
3
|
export const SUPABASE_PUBLISHABLE_KEY = "sb_publishable_af-lUNI2FqEcb-oGy-4uxQ_cnm6kY85";
|
|
4
4
|
export const PACKAGE_NAME = "@somacheck/vibecheck";
|
|
5
|
-
export const PACKAGE_VERSION = "0.
|
|
5
|
+
export const PACKAGE_VERSION = "0.5.0";
|
|
6
6
|
export const PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
7
7
|
export const MCP_SERVER_NAME = "vibecheck";
|
|
8
8
|
export const LEGACY_HOSTED_MCP_SERVER_NAME = "somacheck";
|
|
9
|
-
export const BACKEND_PROTOCOL_VERSION =
|
|
10
|
-
export const TOOLSET_VERSION = "vibecheck-0.
|
|
9
|
+
export const BACKEND_PROTOCOL_VERSION = 4;
|
|
10
|
+
export const TOOLSET_VERSION = "vibecheck-0.5";
|
|
11
11
|
export const TOOL_NAMES = [
|
|
12
12
|
"get_vibecheck_context",
|
|
13
13
|
"get_vibecheck_status",
|
|
14
|
+
"share_somacheck_context",
|
|
14
15
|
"post_vibecheck_statement",
|
|
15
16
|
"get_vibecheck_result",
|
|
16
17
|
];
|
package/dist/link.js
CHANGED
|
@@ -54,7 +54,7 @@ export async function linkAgent(code, dependencies) {
|
|
|
54
54
|
throw new LinkPersistenceError();
|
|
55
55
|
}
|
|
56
56
|
const token = await dependencies.redeem(normalizedCode);
|
|
57
|
-
await writeConfig(dependencies.home, { token });
|
|
57
|
+
await writeConfig(dependencies.home, { token }, dependencies.client);
|
|
58
58
|
dependencies.output("SomaCheck pairing code redeemed on this computer.");
|
|
59
59
|
}
|
|
60
60
|
//# sourceMappingURL=link.js.map
|
package/dist/readiness.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readConfig } from "./config.js";
|
|
2
2
|
import { SomaCheckCompatibilityError, SomaCheckHttpError } from "./api.js";
|
|
3
3
|
import { clientDisplayName, clientRegistrationState, detectLegacyHostedRegistration, isClientInstalled, manualLegacyHostedRemoveCommand, manualRemoveCommand, manualSetupCommand, } from "./client-setup.js";
|
|
4
|
+
import { BACKEND_PROTOCOL_VERSION, PACKAGE_VERSION } from "./constants.js";
|
|
4
5
|
export async function checkReadiness(dependencies) {
|
|
5
6
|
let token;
|
|
6
7
|
try {
|
|
@@ -62,7 +63,7 @@ export async function checkReadiness(dependencies) {
|
|
|
62
63
|
? " It currently reports: Needs authentication."
|
|
63
64
|
: "";
|
|
64
65
|
dependencies.output(`○ ${clientDisplayName(client)} also has a separate legacy server named "somacheck".${authNote}`);
|
|
65
|
-
dependencies.output(
|
|
66
|
+
dependencies.output(` SomaCheck MCP ${PACKAGE_VERSION} uses "vibecheck"; the legacy entry was not changed.`);
|
|
66
67
|
dependencies.output(` Optional manual cleanup: ${manualLegacyHostedRemoveCommand(client)}`);
|
|
67
68
|
}
|
|
68
69
|
const registration = await clientRegistrationState(client, dependencies.runner);
|
|
@@ -81,7 +82,7 @@ export async function checkReadiness(dependencies) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
if (registeredClients.length === 0) {
|
|
84
|
-
dependencies.output(
|
|
85
|
+
dependencies.output(`✗ No supported agent client has the current SomaCheck ${PACKAGE_VERSION} configuration.`);
|
|
85
86
|
if (installedClientCount === 0) {
|
|
86
87
|
dependencies.output(" Install Codex or Claude Code, then run the matching setup command.");
|
|
87
88
|
}
|
|
@@ -101,7 +102,7 @@ export async function checkReadiness(dependencies) {
|
|
|
101
102
|
if (handshake.readiness_status === "ready") {
|
|
102
103
|
backendCompatible = true;
|
|
103
104
|
readyClients.push(client);
|
|
104
|
-
dependencies.output(`✓ ${clientDisplayName(client)} completed the SomaCheck protocol
|
|
105
|
+
dependencies.output(`✓ ${clientDisplayName(client)} completed the SomaCheck protocol-${BACKEND_PROTOCOL_VERSION} health check.`);
|
|
105
106
|
}
|
|
106
107
|
else {
|
|
107
108
|
dependencies.output(`✗ ${clientDisplayName(client)} is not ready yet (${handshake.readiness_status}).`);
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { StatementPendingError } from "./vibecheck.js";
|
|
4
4
|
import { SomaCheckCompatibilityError, SomaCheckHttpError } from "./api.js";
|
|
5
|
-
import { PACKAGE_NAME, PACKAGE_VERSION } from "./constants.js";
|
|
5
|
+
import { PACKAGE_NAME, PACKAGE_SPEC, PACKAGE_VERSION } from "./constants.js";
|
|
6
6
|
const cadenceSchema = z.object({
|
|
7
7
|
reason: z.string(),
|
|
8
8
|
create_due_at: z.string().nullable(),
|
|
@@ -48,10 +48,29 @@ const contextItemSchema = z.object({
|
|
|
48
48
|
answered_at: z.string().datetime({ offset: true }),
|
|
49
49
|
});
|
|
50
50
|
const contextSchema = { checkins: z.array(contextItemSchema) };
|
|
51
|
+
const agentContextTypeSchema = z.enum([
|
|
52
|
+
"goal", "project", "decision", "commitment", "tension", "question",
|
|
53
|
+
"blindSpot", "relationship", "situation", "change", "deadline",
|
|
54
|
+
"development", "moodTheme", "preference",
|
|
55
|
+
]);
|
|
56
|
+
const agentContextObservationSchema = z.object({
|
|
57
|
+
type: agentContextTypeSchema,
|
|
58
|
+
summary: z.string().trim().min(1).max(500)
|
|
59
|
+
.describe("A concise derived theme or fact the person authorized you to share. Never paste raw conversations."),
|
|
60
|
+
confidence: z.number().min(0).max(1).default(0.8),
|
|
61
|
+
evidence_count: z.number().int().min(1).max(100).default(1),
|
|
62
|
+
});
|
|
63
|
+
const contextShareSchema = {
|
|
64
|
+
source_id: z.string(), entry_id: z.string(), accepted: z.boolean(),
|
|
65
|
+
state: z.literal("ready"), observation_count: z.number().int().min(1).max(20),
|
|
66
|
+
captured_at: z.string().datetime({ offset: true }),
|
|
67
|
+
};
|
|
51
68
|
const SERVER_INSTRUCTIONS = [
|
|
52
69
|
"SomaCheck lets you offer the person a statement to test through a quick phone check-in.",
|
|
53
70
|
"Call get_vibecheck_context and get_vibecheck_status at the start of a session or background run, and call status again after a completed check-in.",
|
|
54
71
|
"Treat gesture outcomes as contextual signals, never fixed facts or blanket authorization.",
|
|
72
|
+
"When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
|
|
73
|
+
"Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
|
|
55
74
|
"Maintain three distinct propositions drawn from your own context about this person.",
|
|
56
75
|
"When propositions_needed is greater than zero, post exactly that many in one call.",
|
|
57
76
|
"Offer each statement as something to test, never as a claim of fact about the user.",
|
|
@@ -111,6 +130,32 @@ export function createVibecheckServer(dependencies) {
|
|
|
111
130
|
return failureMessage("read SomaCheck status", error);
|
|
112
131
|
}
|
|
113
132
|
});
|
|
133
|
+
server.registerTool("share_somacheck_context", {
|
|
134
|
+
title: "Share SomaCheck Context",
|
|
135
|
+
description: "Share 1-20 concise, user-authorized context observations so SomaCheck can prepare richer propositions. Send derived summaries only—never raw conversation text, photos, credentials, identifiers, or diagnostic claims.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
observations: z.array(agentContextObservationSchema).min(1).max(20),
|
|
138
|
+
},
|
|
139
|
+
outputSchema: contextShareSchema,
|
|
140
|
+
annotations: {
|
|
141
|
+
readOnlyHint: false,
|
|
142
|
+
destructiveHint: false,
|
|
143
|
+
idempotentHint: true,
|
|
144
|
+
openWorldHint: false,
|
|
145
|
+
},
|
|
146
|
+
}, async ({ observations }) => {
|
|
147
|
+
try {
|
|
148
|
+
const token = await dependencies.loadToken();
|
|
149
|
+
const result = await dependencies.api.shareContext(token, observations);
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: `Shared ${result.observation_count} bounded context observation${result.observation_count === 1 ? "" : "s"}. SomaCheck can now prepare contextual propositions.` }],
|
|
152
|
+
structuredContent: { ...result },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
return failureMessage("share context with SomaCheck", error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
114
159
|
server.registerTool("post_vibecheck_statement", {
|
|
115
160
|
title: "Post Vibecheck Statement",
|
|
116
161
|
description: "Fill the person's SomaCheck cache with one to three personalized statements and return immediately. Call get_vibecheck_status first and submit exactly propositions_needed statements.",
|
|
@@ -202,12 +247,15 @@ function failureMessage(operation, error) {
|
|
|
202
247
|
return failure(operationalFailureText(operation, error));
|
|
203
248
|
}
|
|
204
249
|
function operationalFailureText(operation, error) {
|
|
205
|
-
const doctor =
|
|
250
|
+
const doctor = `Run: npx -y ${PACKAGE_SPEC} doctor`;
|
|
206
251
|
if (error instanceof SomaCheckCompatibilityError) {
|
|
207
252
|
return `Could not ${operation}: this MCP package and the SomaCheck backend are incompatible. ${doctor}`;
|
|
208
253
|
}
|
|
209
254
|
if (error instanceof SomaCheckHttpError) {
|
|
210
255
|
if (error.status === 401 || error.status === 403) {
|
|
256
|
+
if (operation === "share context with SomaCheck") {
|
|
257
|
+
return `Could not ${operation}: context is not enabled for this link, or the link was rejected or revoked. Open SomaCheck → Connect context, then retry. ${doctor}`;
|
|
258
|
+
}
|
|
211
259
|
return `Could not ${operation}: this SomaCheck link was rejected or revoked. Reconnect in the app. ${doctor}`;
|
|
212
260
|
}
|
|
213
261
|
if (error.status === 404) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somacheck/vibecheck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Ask your embodied sense for an answer through SomaCheck.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/sensie-app/Somacheck.git",
|
|
8
|
+
"directory": "packages/vibecheck"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"bin": {
|
|
7
12
|
"vibecheck": "dist/cli.js"
|
|
@@ -13,8 +18,8 @@
|
|
|
13
18
|
"dist/*.js"
|
|
14
19
|
],
|
|
15
20
|
"scripts": {
|
|
16
|
-
"build": "tsc -p tsconfig.json",
|
|
17
|
-
"test": "npm run build && node --import tsx --test test/*.test.ts && node --test test
|
|
21
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
22
|
+
"test": "npm run build && node --import tsx --test test/*.test.ts && node --test test/*.test.mjs",
|
|
18
23
|
"test:release-artifacts": "bash ./verify-release-artifacts.sh",
|
|
19
24
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
25
|
"prepack": "npm run build"
|