@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 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.3.0 link <CODE> --client <CLIENT>`
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 `3` / toolset `vibecheck-0.3`, and both status and context
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 create a
36
- statement for the user to test, read its outcome, recover recent completed
37
- gesture context, and keep a three-statement feed stocked. That is the whole
38
- surface.
39
-
40
- **What is sent.** A question string you supply, and a bearer token identifying
41
- the link. **What is never sent: raw sensor data.** The agent receives a verdict
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.3.0 setup codex
76
- npx -y @somacheck/vibecheck@0.3.0 setup claude
77
- npx -y @somacheck/vibecheck@0.3.0 doctor
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.3.0", "serve", "--client", "codex"]
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.3.0 serve --client claude`
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 not part of this local 0.3 release. The setup command
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
- const rows = await this.#rpcRows("agent_proposition_batch_create", { token, statements });
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
- const row = await this.#rpc("agent_proposition_result", { token, proposition_id: requestId });
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 parseStatus(await this.#rpc("agent_proposition_cache_status", { token }));
271
+ return decodeVibecheckStatus(await this.#rpc("agent_proposition_cache_status", { token }));
216
272
  }
217
273
  async contextRequest(token, limit = 20) {
218
- const rows = await this.#rpcRows("agent_proposition_context", { token, p_limit: limit });
219
- return rows.map((row) => {
220
- const verdict = requiredString(row, "verdict");
221
- const confidence = requiredNumber(row, "confidence");
222
- if (!VERDICTS.has(verdict)) {
223
- throw new Error("SomaCheck returned an invalid verdict.");
224
- }
225
- if (confidence < 0 || confidence > 1) {
226
- throw new Error("SomaCheck returned an invalid confidence.");
227
- }
228
- return {
229
- request_id: requiredString(row, "request_id"),
230
- statement: requiredString(row, "statement"),
231
- verdict: verdict,
232
- confidence,
233
- answered_at: requiredTimestamp(row, "answered_at"),
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("agent_client_handshake", {
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
- " npx -y @somacheck/vibecheck@0.3.0 link <CODE> --client claude",
112
- " npx -y @somacheck/vibecheck@0.3.0 link <CODE> --client codex",
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 0.3: ${manualSetupCommand(client)}`]
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
- preflight: async () => {
206
- await preflightLinkClients(requestedOrPromptedClients, interactive);
207
- },
213
+ client: selectedClients[0],
208
214
  });
209
215
  return (await runDoctor()) ? 0 : 2;
210
216
  }
@@ -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-2]\.\d+$/.test(packageArgs[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" || !("token" in value)) {
31
+ if (value === null || typeof value !== "object") {
32
32
  throw new Error("SomaCheck link configuration is invalid.");
33
33
  }
34
- const token = value.token;
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
- await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, {
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.3.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 = 3;
10
- export const TOOLSET_VERSION = "vibecheck-0.3";
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(' SomaCheck MCP 0.3 uses "vibecheck"; the legacy entry was not changed.');
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("✗ No supported agent client has the current SomaCheck 0.3 configuration.");
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-3 health check.`);
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 = "Run: npx -y @somacheck/vibecheck@0.3.0 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.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/server.test.mjs",
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"