@somacheck/vibecheck 0.5.0 → 0.6.1
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 +44 -8
- package/dist/api.js +118 -1
- package/dist/cli.js +3 -0
- package/dist/client-setup.js +25 -3
- package/dist/constants.js +4 -3
- package/dist/server.js +265 -6
- package/package.json +2 -2
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.6.1 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 `5` / toolset `vibecheck-0.6`, 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
|
|
@@ -35,7 +35,9 @@ fails, the pairing code is not used. Non-interactive implicit selection,
|
|
|
35
35
|
**What the server does once running.** It exposes MCP tools that share bounded,
|
|
36
36
|
user-authorized context summaries, create a statement for the user to test,
|
|
37
37
|
read its outcome, recover recent completed gesture context, and keep a
|
|
38
|
-
three-statement feed stocked.
|
|
38
|
+
three-statement feed stocked. Version 0.6 adds `request_vibecheck` for a
|
|
39
|
+
person-approved, immediate phone check-in. That path is deliberately separate
|
|
40
|
+
from the reflection feed.
|
|
39
41
|
|
|
40
42
|
**What is sent.** A statement string, bounded derived context observations the
|
|
41
43
|
agent explicitly shares, and a bearer token identifying the link. Tool
|
|
@@ -76,9 +78,9 @@ before the pairing code is redeemed.
|
|
|
76
78
|
To configure or repair a client later:
|
|
77
79
|
|
|
78
80
|
```text
|
|
79
|
-
npx -y @somacheck/vibecheck@0.
|
|
80
|
-
npx -y @somacheck/vibecheck@0.
|
|
81
|
-
npx -y @somacheck/vibecheck@0.
|
|
81
|
+
npx -y @somacheck/vibecheck@0.6.1 setup codex
|
|
82
|
+
npx -y @somacheck/vibecheck@0.6.1 setup claude
|
|
83
|
+
npx -y @somacheck/vibecheck@0.6.1 doctor
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
Manual registration remains available:
|
|
@@ -88,13 +90,13 @@ Manual registration remains available:
|
|
|
88
90
|
"mcpServers": {
|
|
89
91
|
"vibecheck": {
|
|
90
92
|
"command": "npx",
|
|
91
|
-
"args": ["-y", "@somacheck/vibecheck@0.
|
|
93
|
+
"args": ["-y", "@somacheck/vibecheck@0.6.1", "serve", "--client", "codex"]
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.
|
|
99
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.1 serve --client claude`
|
|
98
100
|
|
|
99
101
|
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
100
102
|
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
@@ -114,6 +116,7 @@ will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
|
114
116
|
| `share_somacheck_context` | Share 1–20 bounded, derived, user-authorized observations; raw conversations and secrets are rejected. |
|
|
115
117
|
| `post_vibecheck_statement` | Add one to three personalized statements without blocking; returns a stable `request_id` for each. |
|
|
116
118
|
| `get_vibecheck_result` | Read that exact proposition once by `request_id`; returns queued, pending, or its terminal result. |
|
|
119
|
+
| `request_vibecheck` | With per-ask consent, send one immediate phone vibecheck, wait boundedly for its exact answer, and retain an opaque `live:<uuid>` fallback handle. |
|
|
117
120
|
|
|
118
121
|
## How to use it well
|
|
119
122
|
|
|
@@ -123,6 +126,39 @@ prompt is **a statement for the person to test**, not a question about facts:
|
|
|
123
126
|
something to test, never as a claim about them. If it is wrong, their body says
|
|
124
127
|
`unaligned`, and that is a useful answer rather than a failure.
|
|
125
128
|
|
|
129
|
+
Setup and linking are not permission for future asks. If the person's current
|
|
130
|
+
message explicitly asks you to use SomaCheck and includes the exact statement,
|
|
131
|
+
that is consent for this one vibecheck; do not ask them to confirm twice.
|
|
132
|
+
Otherwise, first show the exact statement and invite the person plainly:
|
|
133
|
+
|
|
134
|
+
> Want a quick vibecheck before I continue? I'll send this statement to your
|
|
135
|
+
> phone; you answer with your wrist gesture, and I'll use the result as a
|
|
136
|
+
> signal—not a verdict.
|
|
137
|
+
|
|
138
|
+
Call `request_vibecheck` with `human_confirmed: true` and a new UUID in
|
|
139
|
+
`idempotency_key` only after one of those per-ask consent paths. Reuse that UUID
|
|
140
|
+
only when retrying the same statement. The tool keeps the one created request
|
|
141
|
+
open for a bounded wait and returns `completed` when the exact answer arrives.
|
|
142
|
+
On timeout or a result-read failure it returns the same pending `live:<uuid>`
|
|
143
|
+
handle; pass that handle to `get_vibecheck_result` once later. Caller
|
|
144
|
+
cancellation stops result reads and preserves the backend request and
|
|
145
|
+
idempotency key, but the cancelled MCP transport may suppress that pending
|
|
146
|
+
response. Do not continuously poll or re-ask the same decision to obtain a
|
|
147
|
+
preferred result.
|
|
148
|
+
|
|
149
|
+
The 45-second whole-call deadline also covers the one create request. If that
|
|
150
|
+
network request is aborted after an ambiguous commit, retry the exact same
|
|
151
|
+
statement with the same `idempotency_key`; never create a replacement ask.
|
|
152
|
+
|
|
153
|
+
After a completed result, explain what the signal changes in your next action.
|
|
154
|
+
Never treat it as authorization or say that the person's body made the
|
|
155
|
+
decision. A typed response from the person always overrides the signal.
|
|
156
|
+
|
|
157
|
+
An immediate vibecheck is not proposition caching. It sends one person-approved statement
|
|
158
|
+
for the current choice, expires, and remains bound to its originating client.
|
|
159
|
+
The reflection feed remains asynchronous and keeps its existing three-item
|
|
160
|
+
cache behavior.
|
|
161
|
+
|
|
126
162
|
The agent maintains three distinct insights. SomaCheck presents one at a time;
|
|
127
163
|
the other two remain cached. After a completed gesture, the next cached insight
|
|
128
164
|
is promoted and the open slot is marked for replenishment automatically. Cached insights
|
package/dist/api.js
CHANGED
|
@@ -2,6 +2,10 @@ import { StatementPendingError, } from "./vibecheck.js";
|
|
|
2
2
|
import { BACKEND_PROTOCOL_VERSION, PACKAGE_VERSION, TOOLSET_VERSION } from "./constants.js";
|
|
3
3
|
const STATEMENT_PENDING_MARKER = "SC_VC_STATEMENT_PENDING";
|
|
4
4
|
const UPGRADE_REQUIRED_MARKER = "SC_VC_UPGRADE_REQUIRED";
|
|
5
|
+
const LIVE_ASK_CONFLICT_MARKER = "SC_VC_VIBECHECK_IDEMPOTENCY_CONFLICT";
|
|
6
|
+
const LIVE_ASK_PENDING_MARKER = "SC_VC_VIBECHECK_PENDING";
|
|
7
|
+
const LIVE_ASK_CLIENT_REQUIRED_MARKER = "SC_VC_VIBECHECK_CLIENT_REQUIRED";
|
|
8
|
+
const LIVE_ASK_CLIENT_NOT_READY_MARKER = "SC_VC_VIBECHECK_CLIENT_NOT_READY";
|
|
5
9
|
const STATUSES = new Set(["queued", "pending", "answered", "expired", "cancelled"]);
|
|
6
10
|
const VERDICTS = new Set(["aligned", "unaligned"]);
|
|
7
11
|
const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
@@ -10,6 +14,9 @@ const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
|
10
14
|
"setup_incomplete",
|
|
11
15
|
"health_check_failed",
|
|
12
16
|
]);
|
|
17
|
+
const PUSH_STATUSES = new Set([
|
|
18
|
+
"queued", "processing", "sent", "failed", "skipped",
|
|
19
|
+
]);
|
|
13
20
|
const CONTEXT_CATEGORY = {
|
|
14
21
|
goal: "goals_projects", project: "goals_projects",
|
|
15
22
|
decision: "decisions_commitments", commitment: "decisions_commitments",
|
|
@@ -40,6 +47,24 @@ export class SomaCheckCompatibilityError extends Error {
|
|
|
40
47
|
this.name = "SomaCheckCompatibilityError";
|
|
41
48
|
}
|
|
42
49
|
}
|
|
50
|
+
export class SomaCheckLiveAskConflictError extends Error {
|
|
51
|
+
constructor() {
|
|
52
|
+
super("That Live Ask retry key is already bound to a different statement.");
|
|
53
|
+
this.name = "SomaCheckLiveAskConflictError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class SomaCheckLiveAskPendingError extends Error {
|
|
57
|
+
constructor() {
|
|
58
|
+
super("A vibecheck is already waiting for this connection.");
|
|
59
|
+
this.name = "SomaCheckLiveAskPendingError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class SomaCheckLiveAskClientError extends Error {
|
|
63
|
+
constructor() {
|
|
64
|
+
super("Live Ask requires an exact ready client connection.");
|
|
65
|
+
this.name = "SomaCheckLiveAskClientError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
43
68
|
function firstRow(value) {
|
|
44
69
|
const row = Array.isArray(value) ? value[0] : value;
|
|
45
70
|
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
@@ -155,6 +180,39 @@ export function decodeCreatedRequests(value) {
|
|
|
155
180
|
};
|
|
156
181
|
});
|
|
157
182
|
}
|
|
183
|
+
/** Strict decoder for agent_vibecheck_create. PostgREST wraps the
|
|
184
|
+
* set-returning function in an array. The backend intentionally uses its
|
|
185
|
+
* canonical `answered` lifecycle name; the provider-neutral adapter below
|
|
186
|
+
* maps that to the tool's clearer `completed` state. */
|
|
187
|
+
export function decodeLiveVibecheckCreation(value) {
|
|
188
|
+
const row = firstRow(value);
|
|
189
|
+
const status = requiredString(row, "status");
|
|
190
|
+
if (status !== "pending" && status !== "answered" && status !== "expired" && status !== "cancelled") {
|
|
191
|
+
throw new Error("SomaCheck returned an invalid vibecheck status.");
|
|
192
|
+
}
|
|
193
|
+
if (requiredString(row, "request_kind") !== "live_ask") {
|
|
194
|
+
throw new Error("SomaCheck returned an invalid vibecheck request kind.");
|
|
195
|
+
}
|
|
196
|
+
const pushStatus = nullableString(row, "push_status");
|
|
197
|
+
if (pushStatus !== null && !PUSH_STATUSES.has(pushStatus)) {
|
|
198
|
+
throw new Error("SomaCheck returned an invalid vibecheck delivery state.");
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
request_id: requiredString(row, "request_id"),
|
|
202
|
+
status,
|
|
203
|
+
expires_at: requiredTimestamp(row, "expires_at"),
|
|
204
|
+
idempotent_replay: requiredBoolean(row, "idempotent_replay"),
|
|
205
|
+
push_status: pushStatus,
|
|
206
|
+
cooldown_until: nullableTimestamp(row, "cooldown_until"),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function nullableTimestamp(row, key) {
|
|
210
|
+
const value = nullableString(row, key);
|
|
211
|
+
if (value !== null && !Number.isFinite(Date.parse(value))) {
|
|
212
|
+
throw new Error("SomaCheck returned an invalid timestamp.");
|
|
213
|
+
}
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
158
216
|
export function decodePollResponse(value) {
|
|
159
217
|
const row = asObject(value);
|
|
160
218
|
const status = row.status;
|
|
@@ -188,6 +246,21 @@ export function decodePollResponse(value) {
|
|
|
188
246
|
latency_s: latency,
|
|
189
247
|
};
|
|
190
248
|
}
|
|
249
|
+
export function decodeLiveVibecheckResult(value, expectedRequestId) {
|
|
250
|
+
const row = firstRow(value);
|
|
251
|
+
if (requiredString(row, "request_id") !== expectedRequestId) {
|
|
252
|
+
throw new Error("SomaCheck returned a mismatched vibecheck result.");
|
|
253
|
+
}
|
|
254
|
+
if (requiredString(row, "request_kind") !== "live_ask") {
|
|
255
|
+
throw new Error("SomaCheck returned an invalid vibecheck request kind.");
|
|
256
|
+
}
|
|
257
|
+
const result = decodePollResponse(row);
|
|
258
|
+
if (result.status === "queued") {
|
|
259
|
+
throw new Error("SomaCheck returned an invalid vibecheck status.");
|
|
260
|
+
}
|
|
261
|
+
requiredTimestamp(row, "expires_at");
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
191
264
|
export function decodeVibecheckContext(value) {
|
|
192
265
|
return allRows(value).map((row) => {
|
|
193
266
|
const verdict = requiredString(row, "verdict");
|
|
@@ -261,12 +334,45 @@ export class SupabaseAgentApi {
|
|
|
261
334
|
const row = await this.#rpc("agent_link_redeem", { code });
|
|
262
335
|
return requiredString(row, "token");
|
|
263
336
|
}
|
|
337
|
+
async requestVibecheck(token, identity, statement, idempotencyKey, signal) {
|
|
338
|
+
if (identity.kind !== "local")
|
|
339
|
+
throw new SomaCheckLiveAskClientError();
|
|
340
|
+
const clientKey = identity.client_key;
|
|
341
|
+
const created = decodeLiveVibecheckCreation(await this.#rpcJson("agent_vibecheck_create", {
|
|
342
|
+
token,
|
|
343
|
+
statement,
|
|
344
|
+
idempotency_key: idempotencyKey,
|
|
345
|
+
client_key: clientKey,
|
|
346
|
+
}, signal));
|
|
347
|
+
const lifecycle = created.status === "answered"
|
|
348
|
+
? await this.liveVibecheckResult(token, identity, created.request_id, signal)
|
|
349
|
+
: { status: created.status, verdict: null, confidence: null, latency_s: null };
|
|
350
|
+
return {
|
|
351
|
+
request_id: `live:${created.request_id}`,
|
|
352
|
+
state: lifecycle.status === "answered" ? "completed" : lifecycle.status,
|
|
353
|
+
verdict: lifecycle.verdict,
|
|
354
|
+
confidence: lifecycle.confidence,
|
|
355
|
+
expires_at: created.expires_at,
|
|
356
|
+
idempotent_replay: created.idempotent_replay,
|
|
357
|
+
delivery_state: created.push_status,
|
|
358
|
+
cooldown_until: created.cooldown_until,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
264
361
|
async createRequests(token, statements) {
|
|
265
362
|
return decodeCreatedRequests(await this.#rpcJson("agent_proposition_batch_create", { token, statements }));
|
|
266
363
|
}
|
|
267
364
|
async pollRequest(token, requestId) {
|
|
268
365
|
return decodePollResponse(await this.#rpc("agent_proposition_result", { token, proposition_id: requestId }));
|
|
269
366
|
}
|
|
367
|
+
async liveVibecheckResult(token, identity, requestId, signal) {
|
|
368
|
+
if (identity.kind !== "local")
|
|
369
|
+
throw new SomaCheckLiveAskClientError();
|
|
370
|
+
return decodeLiveVibecheckResult(await this.#rpcJson("agent_vibecheck_result", {
|
|
371
|
+
token,
|
|
372
|
+
live_ask_id: requestId,
|
|
373
|
+
client_key: identity.client_key,
|
|
374
|
+
}, signal), requestId);
|
|
375
|
+
}
|
|
270
376
|
async statusRequest(token) {
|
|
271
377
|
return decodeVibecheckStatus(await this.#rpc("agent_proposition_cache_status", { token }));
|
|
272
378
|
}
|
|
@@ -312,7 +418,7 @@ export class SupabaseAgentApi {
|
|
|
312
418
|
async #rpc(name, body) {
|
|
313
419
|
return firstRow(await this.#rpcJson(name, body));
|
|
314
420
|
}
|
|
315
|
-
async #rpcJson(name, body) {
|
|
421
|
+
async #rpcJson(name, body, signal) {
|
|
316
422
|
const response = await this.#fetch(`${this.#endpoint}/rest/v1/rpc/${name}`, {
|
|
317
423
|
method: "POST",
|
|
318
424
|
headers: {
|
|
@@ -323,12 +429,23 @@ export class SupabaseAgentApi {
|
|
|
323
429
|
"accept-profile": "somacheck_engine",
|
|
324
430
|
},
|
|
325
431
|
body: JSON.stringify(body),
|
|
432
|
+
...(signal === undefined ? {} : { signal }),
|
|
326
433
|
});
|
|
327
434
|
if (!response.ok) {
|
|
328
435
|
const responseBody = await response.text().catch(() => "");
|
|
329
436
|
if (response.status === 426 || responseBody.includes(UPGRADE_REQUIRED_MARKER)) {
|
|
330
437
|
throw new SomaCheckCompatibilityError(null, null, null);
|
|
331
438
|
}
|
|
439
|
+
if (response.status === 409 && responseBody.includes(LIVE_ASK_CONFLICT_MARKER)) {
|
|
440
|
+
throw new SomaCheckLiveAskConflictError();
|
|
441
|
+
}
|
|
442
|
+
if (response.status === 409 && responseBody.includes(LIVE_ASK_PENDING_MARKER)) {
|
|
443
|
+
throw new SomaCheckLiveAskPendingError();
|
|
444
|
+
}
|
|
445
|
+
if (responseBody.includes(LIVE_ASK_CLIENT_REQUIRED_MARKER)
|
|
446
|
+
|| responseBody.includes(LIVE_ASK_CLIENT_NOT_READY_MARKER)) {
|
|
447
|
+
throw new SomaCheckLiveAskClientError();
|
|
448
|
+
}
|
|
332
449
|
if (response.status === 409 || responseBody.includes(STATEMENT_PENDING_MARKER)) {
|
|
333
450
|
throw new StatementPendingError();
|
|
334
451
|
}
|
package/dist/cli.js
CHANGED
|
@@ -182,6 +182,9 @@ async function startServer(runtimeClient) {
|
|
|
182
182
|
const server = createVibecheckServer({
|
|
183
183
|
api,
|
|
184
184
|
loadToken: async () => (await readConfig(homedir(), runtimeClient ?? undefined)).token,
|
|
185
|
+
identity: runtimeClient === null
|
|
186
|
+
? { kind: "legacy" }
|
|
187
|
+
: { kind: "local", client_key: runtimeClient },
|
|
185
188
|
});
|
|
186
189
|
await server.connect(new StdioServerTransport());
|
|
187
190
|
if (runtimeClient !== null) {
|
package/dist/client-setup.js
CHANGED
|
@@ -91,6 +91,8 @@ function registrationMatches(client, stdout) {
|
|
|
91
91
|
return false;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
if (/^\s*(?:Enabled|Status):\s*(?:false|disabled|off)\s*$/im.test(stdout))
|
|
95
|
+
return false;
|
|
94
96
|
const command = /^\s*Command:\s*(\S+)\s*$/im.exec(stdout)?.[1];
|
|
95
97
|
const rawArgs = /^\s*Args:\s*(.*?)\s*$/im.exec(stdout)?.[1] ?? "";
|
|
96
98
|
const parsedArgs = rawArgs.split(/\s+/).filter(Boolean);
|
|
@@ -117,15 +119,35 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
else {
|
|
122
|
+
if (/^\s*(?:Enabled|Status):\s*(?:false|disabled|off)\s*$/im.test(stdout))
|
|
123
|
+
return false;
|
|
120
124
|
command = /^\s*Command:\s*(\S+)\s*$/im.exec(stdout)?.[1];
|
|
121
125
|
args = (/^\s*Args:\s*(.*?)\s*$/im.exec(stdout)?.[1] ?? "").split(/\s+/).filter(Boolean);
|
|
122
126
|
}
|
|
123
127
|
if (command !== "npx")
|
|
124
128
|
return false;
|
|
125
129
|
const packageArgs = args.filter((arg) => arg.startsWith("@somacheck/vibecheck@"));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
if (packageArgs.length !== 1)
|
|
131
|
+
return false;
|
|
132
|
+
const packageArg = packageArgs[0];
|
|
133
|
+
const legacyWithoutClient = /^@somacheck\/vibecheck@0\.[0-3]\.\d+$/.test(packageArg)
|
|
134
|
+
&& args.every((arg) => arg === "-y" || arg === "--yes" || arg === packageArg);
|
|
135
|
+
if (legacyWithoutClient)
|
|
136
|
+
return true;
|
|
137
|
+
// 0.5 introduced the exact local runtime identity required by Live Ask.
|
|
138
|
+
// It is safe to replace only the canonical command generated by 0.5 or the
|
|
139
|
+
// exact 0.6.0 release being patched. Custom wrappers, extra arguments,
|
|
140
|
+
// disabled entries, later versions, and registrations naming the other
|
|
141
|
+
// client remain user-owned.
|
|
142
|
+
const previousExactClient = (/^@somacheck\/vibecheck@0\.5\.\d+$/.test(packageArg)
|
|
143
|
+
|| packageArg === "@somacheck/vibecheck@0.6.0")
|
|
144
|
+
&& args.length === 5
|
|
145
|
+
&& args[0] === "-y"
|
|
146
|
+
&& args[1] === packageArg
|
|
147
|
+
&& args[2] === "serve"
|
|
148
|
+
&& args[3] === "--client"
|
|
149
|
+
&& args[4] === client;
|
|
150
|
+
return previousExactClient;
|
|
129
151
|
}
|
|
130
152
|
export async function isClientRegistered(client, runner) {
|
|
131
153
|
return (await clientRegistrationState(client, runner)) === "current";
|
package/dist/constants.js
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
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.6.1";
|
|
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 = 5;
|
|
10
|
+
export const TOOLSET_VERSION = "vibecheck-0.6";
|
|
11
11
|
export const TOOL_NAMES = [
|
|
12
12
|
"get_vibecheck_context",
|
|
13
13
|
"get_vibecheck_status",
|
|
14
14
|
"share_somacheck_context",
|
|
15
15
|
"post_vibecheck_statement",
|
|
16
16
|
"get_vibecheck_result",
|
|
17
|
+
"request_vibecheck",
|
|
17
18
|
];
|
|
18
19
|
//# sourceMappingURL=constants.js.map
|
package/dist/server.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { StatementPendingError } from "./vibecheck.js";
|
|
4
|
-
import { SomaCheckCompatibilityError, SomaCheckHttpError } from "./api.js";
|
|
3
|
+
import { StatementPendingError, } from "./vibecheck.js";
|
|
4
|
+
import { SomaCheckCompatibilityError, SomaCheckHttpError, SomaCheckLiveAskClientError, SomaCheckLiveAskConflictError, SomaCheckLiveAskPendingError, } from "./api.js";
|
|
5
5
|
import { PACKAGE_NAME, PACKAGE_SPEC, PACKAGE_VERSION } from "./constants.js";
|
|
6
|
+
const LIVE_ASK_WAIT_BUDGET_MS = 45_000;
|
|
7
|
+
const LIVE_ASK_POLL_DELAYS_MS = [
|
|
8
|
+
1_000, 1_000, 2_000, 2_000, 3_000, 3_000,
|
|
9
|
+
4_000, 4_000, 5_000, 5_000, 5_000, 5_000,
|
|
10
|
+
];
|
|
11
|
+
function abortAwareSleep(milliseconds, signal) {
|
|
12
|
+
if (signal.aborted || milliseconds <= 0)
|
|
13
|
+
return Promise.resolve();
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let timer;
|
|
16
|
+
const finish = () => {
|
|
17
|
+
if (timer !== undefined)
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
signal.removeEventListener("abort", finish);
|
|
20
|
+
resolve();
|
|
21
|
+
};
|
|
22
|
+
timer = setTimeout(finish, milliseconds);
|
|
23
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
6
26
|
const cadenceSchema = z.object({
|
|
7
27
|
reason: z.string(),
|
|
8
28
|
create_due_at: z.string().nullable(),
|
|
@@ -48,6 +68,28 @@ const contextItemSchema = z.object({
|
|
|
48
68
|
answered_at: z.string().datetime({ offset: true }),
|
|
49
69
|
});
|
|
50
70
|
const contextSchema = { checkins: z.array(contextItemSchema) };
|
|
71
|
+
const liveAskErrorCodeSchema = z.enum([
|
|
72
|
+
"link_revoked",
|
|
73
|
+
"upgrade_required",
|
|
74
|
+
"setup_required",
|
|
75
|
+
"request_conflict",
|
|
76
|
+
"vibecheck_pending",
|
|
77
|
+
"rate_limited",
|
|
78
|
+
"live_ask_unavailable",
|
|
79
|
+
"backend_unavailable",
|
|
80
|
+
"connection_failed",
|
|
81
|
+
]);
|
|
82
|
+
const liveAskSchema = {
|
|
83
|
+
state: z.enum(["pending", "completed", "expired", "cancelled", "error"]),
|
|
84
|
+
request_id: z.string().nullable(),
|
|
85
|
+
verdict: z.enum(["aligned", "unaligned"]).nullable(),
|
|
86
|
+
confidence: z.number().min(0).max(1).nullable(),
|
|
87
|
+
expires_at: z.string().datetime({ offset: true }).nullable(),
|
|
88
|
+
idempotent_replay: z.boolean().nullable(),
|
|
89
|
+
delivery_state: z.enum(["queued", "processing", "sent", "failed", "skipped"]).nullable(),
|
|
90
|
+
cooldown_until: z.string().datetime({ offset: true }).nullable(),
|
|
91
|
+
error_code: liveAskErrorCodeSchema.nullable(),
|
|
92
|
+
};
|
|
51
93
|
const agentContextTypeSchema = z.enum([
|
|
52
94
|
"goal", "project", "decision", "commitment", "tension", "question",
|
|
53
95
|
"blindSpot", "relationship", "situation", "change", "deadline",
|
|
@@ -69,6 +111,15 @@ const SERVER_INSTRUCTIONS = [
|
|
|
69
111
|
"SomaCheck lets you offer the person a statement to test through a quick phone check-in.",
|
|
70
112
|
"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.",
|
|
71
113
|
"Treat gesture outcomes as contextual signals, never fixed facts or blanket authorization.",
|
|
114
|
+
"Use request_vibecheck when a current choice would benefit from the person's own felt response; never use it to evaluate someone else.",
|
|
115
|
+
"A setup or link is not blanket consent for future vibechecks.",
|
|
116
|
+
"If the person's current message explicitly tells you to request a SomaCheck vibecheck and includes the exact statement, that message is consent for that one ask; set human_confirmed to true and do not ask them to confirm twice.",
|
|
117
|
+
"Otherwise, first show the exact statement and ask: Want a quick vibecheck before I continue? I'll send this statement to your phone; you answer with your wrist gesture, and I'll use the result as a signal—not a verdict. Call only after the person accepts.",
|
|
118
|
+
"Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
|
|
119
|
+
"An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
|
|
120
|
+
"Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
|
|
121
|
+
"request_vibecheck waits boundedly for the exact answer. If it returns completed, continue in the same turn and explain what changes. If it returns pending, retain that exact handle and read it once later; never create a replacement ask.",
|
|
122
|
+
"After a completed result, say what the signal changes in your next action. Never say 'your body decided'; a typed human response always overrides the signal.",
|
|
72
123
|
"When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
|
|
73
124
|
"Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
|
|
74
125
|
"Maintain three distinct propositions drawn from your own context about this person.",
|
|
@@ -189,9 +240,9 @@ export function createVibecheckServer(dependencies) {
|
|
|
189
240
|
});
|
|
190
241
|
server.registerTool("get_vibecheck_result", {
|
|
191
242
|
title: "Get Vibecheck Result",
|
|
192
|
-
description: "Read one proposition by request_id. This is a single non-blocking read: queued is cached, and pending is
|
|
243
|
+
description: "Read one exact proposition or immediate vibecheck by request_id. This is a single non-blocking read: queued is cached, and pending is not answered. Immediate vibecheck handles begin with live: and remain bound to the originating client.",
|
|
193
244
|
inputSchema: {
|
|
194
|
-
request_id: z.string().min(1).describe("The request_id returned by post_vibecheck_statement."),
|
|
245
|
+
request_id: z.string().min(1).describe("The opaque request_id returned by post_vibecheck_statement or request_vibecheck."),
|
|
195
246
|
},
|
|
196
247
|
outputSchema: resultSchema,
|
|
197
248
|
annotations: {
|
|
@@ -203,10 +254,19 @@ export function createVibecheckServer(dependencies) {
|
|
|
203
254
|
}, async ({ request_id }) => {
|
|
204
255
|
try {
|
|
205
256
|
const token = await dependencies.loadToken();
|
|
206
|
-
const
|
|
257
|
+
const liveRequestId = parseLiveVibecheckHandle(request_id);
|
|
258
|
+
const identity = dependencies.identity ?? { kind: "legacy" };
|
|
259
|
+
if (liveRequestId !== null && identity.kind === "legacy") {
|
|
260
|
+
return failure("Could not read that vibecheck: Live Ask needs the exact ready client connection that created it. Run setup and restart the client.");
|
|
261
|
+
}
|
|
262
|
+
const result = liveRequestId === null
|
|
263
|
+
? await dependencies.api.pollRequest(token, request_id)
|
|
264
|
+
: await dependencies.api.liveVibecheckResult(token, identity, liveRequestId);
|
|
207
265
|
const structuredContent = { request_id, ...result };
|
|
208
266
|
const text = result.status === "answered"
|
|
209
|
-
?
|
|
267
|
+
? liveRequestId === null
|
|
268
|
+
? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
|
|
269
|
+
: `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
|
|
210
270
|
: result.status === "queued"
|
|
211
271
|
? "Queued. The proposition is cached until the person advances their feed."
|
|
212
272
|
: result.status === "pending"
|
|
@@ -218,8 +278,141 @@ export function createVibecheckServer(dependencies) {
|
|
|
218
278
|
return failureMessage("read that SomaCheck result", error);
|
|
219
279
|
}
|
|
220
280
|
});
|
|
281
|
+
server.registerTool("request_vibecheck", {
|
|
282
|
+
title: "Request a Vibecheck",
|
|
283
|
+
description: "Send one plain-language statement to the person's phone for a vibecheck answered with a wrist gesture. A current user command that explicitly requests SomaCheck and includes the exact statement counts as confirmation for that one ask; do not ask twice. Otherwise, first show the exact statement and call only after the person accepts. Setup or linking is not blanket consent. Use this only when their own felt response would help with the current choice, not for routine feed content or to judge another person. The call waits boundedly for the exact result and returns completed when available. A timeout returns the same stable pending request_id for one later read. Caller cancellation stops result reads without creating another request; the cancelled transport may suppress the pending response.",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
statement: z.string().trim().min(1).max(1000)
|
|
286
|
+
.describe("One plain-language statement for the person to test; offer a signal, not a fact, diagnosis, or instruction."),
|
|
287
|
+
idempotency_key: z.string().uuid()
|
|
288
|
+
.describe("A new UUID for this logical ask. Reuse the same UUID only to retry the exact same statement; retries will not create another phone request."),
|
|
289
|
+
human_confirmed: z.literal(true)
|
|
290
|
+
.describe("Confirms the person accepted this exact vibecheck, either by explicitly requesting SomaCheck with this statement in their current message or by accepting after the agent showed it."),
|
|
291
|
+
},
|
|
292
|
+
outputSchema: liveAskSchema,
|
|
293
|
+
annotations: {
|
|
294
|
+
readOnlyHint: false,
|
|
295
|
+
destructiveHint: false,
|
|
296
|
+
idempotentHint: true,
|
|
297
|
+
openWorldHint: false,
|
|
298
|
+
},
|
|
299
|
+
}, async ({ statement, idempotency_key }, extra) => {
|
|
300
|
+
const now = dependencies.liveAskWait?.now ?? (() => performance.now());
|
|
301
|
+
const budgetMs = dependencies.liveAskWait?.budgetMs ?? LIVE_ASK_WAIT_BUDGET_MS;
|
|
302
|
+
const deadline = now() + budgetMs;
|
|
303
|
+
const callAbort = new AbortController();
|
|
304
|
+
const forwardCallerAbort = () => callAbort.abort(extra.signal.reason);
|
|
305
|
+
if (extra.signal.aborted) {
|
|
306
|
+
forwardCallerAbort();
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
extra.signal.addEventListener("abort", forwardCallerAbort, { once: true });
|
|
310
|
+
}
|
|
311
|
+
const callDeadline = setTimeout(() => callAbort.abort(new Error("Live Ask tool deadline reached.")), budgetMs);
|
|
312
|
+
try {
|
|
313
|
+
const identity = dependencies.identity ?? { kind: "legacy" };
|
|
314
|
+
if (identity.kind === "legacy") {
|
|
315
|
+
return liveAskFailure(new SomaCheckLiveAskClientError());
|
|
316
|
+
}
|
|
317
|
+
const token = await dependencies.loadToken();
|
|
318
|
+
const created = await dependencies.api.requestVibecheck(token, identity, statement.trim(), idempotency_key, callAbort.signal);
|
|
319
|
+
const result = await waitForLiveVibecheck(created, token, identity, dependencies, callAbort.signal, deadline, (attempt) => {
|
|
320
|
+
const progressToken = extra._meta?.progressToken;
|
|
321
|
+
if (progressToken === undefined)
|
|
322
|
+
return;
|
|
323
|
+
void extra.sendNotification({
|
|
324
|
+
method: "notifications/progress",
|
|
325
|
+
params: {
|
|
326
|
+
progressToken,
|
|
327
|
+
progress: attempt,
|
|
328
|
+
total: LIVE_ASK_POLL_DELAYS_MS.length,
|
|
329
|
+
message: "Waiting for the person's vibecheck signal.",
|
|
330
|
+
},
|
|
331
|
+
}).catch(() => {
|
|
332
|
+
// Progress is optional. A host that does not accept it must not
|
|
333
|
+
// prevent the exact result from completing this tool call.
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
const structuredContent = { ...result, error_code: null };
|
|
337
|
+
const text = result.state === "completed"
|
|
338
|
+
? `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
|
|
339
|
+
: result.state === "pending"
|
|
340
|
+
? result.delivery_state === "failed" || result.delivery_state === "skipped"
|
|
341
|
+
? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}. Do not repost it or change the idempotency_key; check the phone setup before a later decision.`
|
|
342
|
+
: `Vibecheck ${result.request_id} is still pending after a bounded wait. Keep this exact handle and check it once later with get_vibecheck_result. It expires at ${result.expires_at}; do not poll continuously or re-ask for this decision.`
|
|
343
|
+
: `This vibecheck is ${result.state}. Do not create another ask for the same decision.`;
|
|
344
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
return liveAskFailure(error);
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
clearTimeout(callDeadline);
|
|
351
|
+
extra.signal.removeEventListener("abort", forwardCallerAbort);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
221
354
|
return server;
|
|
222
355
|
}
|
|
356
|
+
async function waitForLiveVibecheck(created, token, identity, dependencies, signal, deadline, progress) {
|
|
357
|
+
if (created.state !== "pending"
|
|
358
|
+
|| created.delivery_state === "failed"
|
|
359
|
+
|| created.delivery_state === "skipped"
|
|
360
|
+
|| signal.aborted) {
|
|
361
|
+
return created;
|
|
362
|
+
}
|
|
363
|
+
const requestId = parseLiveVibecheckHandle(created.request_id);
|
|
364
|
+
if (requestId === null)
|
|
365
|
+
return created;
|
|
366
|
+
const now = dependencies.liveAskWait?.now ?? (() => performance.now());
|
|
367
|
+
const sleep = dependencies.liveAskWait?.sleep ?? abortAwareSleep;
|
|
368
|
+
for (const [index, delay] of LIVE_ASK_POLL_DELAYS_MS.entries()) {
|
|
369
|
+
const remaining = deadline - now();
|
|
370
|
+
if (signal.aborted || remaining <= 0)
|
|
371
|
+
return created;
|
|
372
|
+
await sleep(Math.min(delay, remaining), signal);
|
|
373
|
+
if (signal.aborted || now() >= deadline)
|
|
374
|
+
return created;
|
|
375
|
+
progress(index + 1);
|
|
376
|
+
if (signal.aborted)
|
|
377
|
+
return created;
|
|
378
|
+
let lifecycle;
|
|
379
|
+
const resultAbort = new AbortController();
|
|
380
|
+
const forwardCallerAbort = () => resultAbort.abort(signal.reason);
|
|
381
|
+
signal.addEventListener("abort", forwardCallerAbort, { once: true });
|
|
382
|
+
const resultTimeout = setTimeout(() => resultAbort.abort(new Error("Live Ask result deadline reached.")), Math.max(0, deadline - now()));
|
|
383
|
+
try {
|
|
384
|
+
lifecycle = await dependencies.api.liveVibecheckResult(token, identity, requestId, resultAbort.signal);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// Creation already succeeded. Keep the exact handle so a transient,
|
|
388
|
+
// cancelled, revoked, or deadline-aborted read cannot prompt a duplicate.
|
|
389
|
+
return created;
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
clearTimeout(resultTimeout);
|
|
393
|
+
signal.removeEventListener("abort", forwardCallerAbort);
|
|
394
|
+
}
|
|
395
|
+
if (signal.aborted)
|
|
396
|
+
return created;
|
|
397
|
+
if (lifecycle.status === "answered") {
|
|
398
|
+
return {
|
|
399
|
+
...created,
|
|
400
|
+
state: "completed",
|
|
401
|
+
verdict: lifecycle.verdict,
|
|
402
|
+
confidence: lifecycle.confidence,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (lifecycle.status === "expired" || lifecycle.status === "cancelled") {
|
|
406
|
+
return {
|
|
407
|
+
...created,
|
|
408
|
+
state: lifecycle.status,
|
|
409
|
+
verdict: null,
|
|
410
|
+
confidence: null,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return created;
|
|
415
|
+
}
|
|
223
416
|
function summarise(status) {
|
|
224
417
|
const cadence = status.cadence;
|
|
225
418
|
if (status.first_run_intro.should_offer_now) {
|
|
@@ -237,6 +430,16 @@ function formatDuration(seconds) {
|
|
|
237
430
|
const hours = seconds / 3600;
|
|
238
431
|
return Number.isInteger(hours) ? `${hours}-hour` : `${Math.round(seconds / 60)}-minute`;
|
|
239
432
|
}
|
|
433
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
434
|
+
function parseLiveVibecheckHandle(handle) {
|
|
435
|
+
if (!handle.startsWith("live:"))
|
|
436
|
+
return null;
|
|
437
|
+
const requestId = handle.slice("live:".length);
|
|
438
|
+
if (!UUID_PATTERN.test(requestId)) {
|
|
439
|
+
throw new Error("The Live Ask request_id is malformed.");
|
|
440
|
+
}
|
|
441
|
+
return requestId;
|
|
442
|
+
}
|
|
240
443
|
function failure(text) {
|
|
241
444
|
return {
|
|
242
445
|
isError: true,
|
|
@@ -246,6 +449,62 @@ function failure(text) {
|
|
|
246
449
|
function failureMessage(operation, error) {
|
|
247
450
|
return failure(operationalFailureText(operation, error));
|
|
248
451
|
}
|
|
452
|
+
function liveAskFailure(error) {
|
|
453
|
+
const doctor = `Run: npx -y ${PACKAGE_SPEC} doctor`;
|
|
454
|
+
let errorCode;
|
|
455
|
+
let text;
|
|
456
|
+
if (error instanceof SomaCheckCompatibilityError) {
|
|
457
|
+
errorCode = "upgrade_required";
|
|
458
|
+
text = `Could not request a vibecheck: this package needs an upgrade before immediate vibechecks can be used. ${doctor}`;
|
|
459
|
+
}
|
|
460
|
+
else if (error instanceof SomaCheckLiveAskClientError) {
|
|
461
|
+
errorCode = "setup_required";
|
|
462
|
+
text = `Could not request a vibecheck: immediate vibechecks need an exact ready agent connection. Run the pinned serve command through setup, then restart the client. ${doctor}`;
|
|
463
|
+
}
|
|
464
|
+
else if (error instanceof SomaCheckLiveAskConflictError) {
|
|
465
|
+
errorCode = "request_conflict";
|
|
466
|
+
text = "Could not request a vibecheck: that retry key already belongs to a different statement. Use the original statement, or use a new UUID for a genuinely new ask.";
|
|
467
|
+
}
|
|
468
|
+
else if (error instanceof SomaCheckLiveAskPendingError) {
|
|
469
|
+
errorCode = "vibecheck_pending";
|
|
470
|
+
text = "Could not request a vibecheck: one is already waiting for this connection. Keep the existing request; do not re-ask or change the retry key.";
|
|
471
|
+
}
|
|
472
|
+
else if (error instanceof SomaCheckHttpError && (error.status === 401 || error.status === 403)) {
|
|
473
|
+
errorCode = "link_revoked";
|
|
474
|
+
text = `Could not request a vibecheck: this SomaCheck link was rejected or revoked. Reconnect in the app. ${doctor}`;
|
|
475
|
+
}
|
|
476
|
+
else if (error instanceof SomaCheckHttpError && error.status === 404) {
|
|
477
|
+
errorCode = "live_ask_unavailable";
|
|
478
|
+
text = `Could not request a vibecheck: immediate vibechecks are not available on this SomaCheck backend yet. ${doctor}`;
|
|
479
|
+
}
|
|
480
|
+
else if (error instanceof SomaCheckHttpError && error.status === 429) {
|
|
481
|
+
errorCode = "rate_limited";
|
|
482
|
+
text = "Could not request a vibecheck: too many vibechecks were requested recently. Wait before making a new ask; do not change the retry key for the same ask.";
|
|
483
|
+
}
|
|
484
|
+
else if (error instanceof SomaCheckHttpError && error.status >= 500) {
|
|
485
|
+
errorCode = "backend_unavailable";
|
|
486
|
+
text = `Could not request a vibecheck: the SomaCheck backend is temporarily unavailable. Retry later with the same idempotency_key. ${doctor}`;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
errorCode = "connection_failed";
|
|
490
|
+
text = `Could not request a vibecheck: the local link or network check failed. Retry later with the same idempotency_key. ${doctor}`;
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
isError: true,
|
|
494
|
+
content: [{ type: "text", text }],
|
|
495
|
+
structuredContent: {
|
|
496
|
+
state: "error",
|
|
497
|
+
request_id: null,
|
|
498
|
+
verdict: null,
|
|
499
|
+
confidence: null,
|
|
500
|
+
expires_at: null,
|
|
501
|
+
idempotent_replay: null,
|
|
502
|
+
delivery_state: null,
|
|
503
|
+
cooldown_until: null,
|
|
504
|
+
error_code: errorCode,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
249
508
|
function operationalFailureText(operation, error) {
|
|
250
509
|
const doctor = `Run: npx -y ${PACKAGE_SPEC} doctor`;
|
|
251
510
|
if (error instanceof SomaCheckCompatibilityError) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somacheck/vibecheck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Send a consented vibecheck to SomaCheck and use the result as a signal.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/sensie-app/Somacheck.git",
|