slashvibe-mcp 0.8.7 → 0.8.9
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/auth-store.js +35 -5
- package/config.js +29 -3
- package/index.js +22 -3
- package/package.json +3 -3
- package/version.json +6 -7
package/auth-store.js
CHANGED
|
@@ -61,13 +61,43 @@ function hydrate() {
|
|
|
61
61
|
const config = require('./config');
|
|
62
62
|
const cfg = config.load();
|
|
63
63
|
|
|
64
|
-
_token = cfg.authToken || cfg.privyToken || null;
|
|
65
64
|
_oneLiner = cfg.one_liner || cfg.workingOn || null;
|
|
66
65
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
// ONE IDENTITY, ONE AUTHORITY — and the authority is whichever credential still WORKS.
|
|
67
|
+
//
|
|
68
|
+
// This used to take cfg.authToken unconditionally and only look at auth.json when
|
|
69
|
+
// config.json had no token at all. So a machine holding a DEAD token in config.json
|
|
70
|
+
// and a LIVE one in auth.json hydrated with the dead one and reported
|
|
71
|
+
// isAuthenticated() === true. Reproduced before this change:
|
|
72
|
+
//
|
|
73
|
+
// hydrate chose: expired | isAuthenticated(): true
|
|
74
|
+
//
|
|
75
|
+
// Presence order is unchanged — config.json still wins when it is usable. What
|
|
76
|
+
// changed is that "present" is no longer mistaken for "usable". Falling back only on
|
|
77
|
+
// ABSENCE means the freshest credential loses to the first one on disk, which is
|
|
78
|
+
// precisely the "several credentials for one identity" failure canon law 3 exists to
|
|
79
|
+
// prevent (§4). With 30-day logins every machine eventually holds a stale one, so
|
|
80
|
+
// this is a matter of when, not whether.
|
|
81
|
+
//
|
|
82
|
+
// Deliberately local: inspectToken decodes `exp` rather than asking the server,
|
|
83
|
+
// because hydrate runs at boot where the network may be the broken thing. It cannot
|
|
84
|
+
// prove a token is GOOD — only the server can, and markVerified() records that later.
|
|
85
|
+
// It reliably rejects one that is definitively over.
|
|
86
|
+
const candidates = [
|
|
87
|
+
cfg.authToken,
|
|
88
|
+
cfg.privyToken,
|
|
89
|
+
config.loadTerminalAuth()?.token,
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
|
|
92
|
+
_token = candidates.find((t) => inspectToken(t).ok) || null;
|
|
93
|
+
|
|
94
|
+
if (!_token && candidates.length > 0) {
|
|
95
|
+
// Every credential we hold is unusable. That is signed OUT, not signed in as the
|
|
96
|
+
// least-bad option — and it must be distinguishable from having none at all, so a
|
|
97
|
+
// remembered handle does not step in to name a session nothing backs (#110).
|
|
98
|
+
const why = inspectToken(candidates[0]).reason;
|
|
99
|
+
console.error(`[auth-store] No usable credential (${candidates.length} found, first is ${why})`);
|
|
100
|
+
_rejected = true;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
103
|
// THE TOKEN IS THE IDENTITY.
|
package/config.js
CHANGED
|
@@ -160,11 +160,26 @@ function getOneLiner() {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function isInitialized() {
|
|
163
|
-
//
|
|
163
|
+
// A VALID CREDENTIAL IS INITIALISATION. Asking the file was the bug.
|
|
164
|
+
//
|
|
165
|
+
// This checked only for a stored handle, so a config that held a perfectly good token
|
|
166
|
+
// and no `username` reported "not initialized" forever. Tools behind requireInit()
|
|
167
|
+
// refused with "Run `vibe init` first" while presence — which resolves identity from
|
|
168
|
+
// the credential — worked in the same response. One reply, two answers about whether
|
|
169
|
+
// you are signed in.
|
|
170
|
+
//
|
|
171
|
+
// It is reachable by accident: save() writes `username: config.handle ||
|
|
172
|
+
// config.username || existing.username`, and when all three are absent that value is
|
|
173
|
+
// `undefined`, which JSON.stringify DROPS. So the key does not go null, it disappears,
|
|
174
|
+
// and nothing puts it back. Observed on this machine 2026-08-01.
|
|
175
|
+
//
|
|
176
|
+
// The credential is the identity (#107). If one names us, we are initialised — and if
|
|
177
|
+
// one does not, no remembered string should pretend otherwise.
|
|
178
|
+
if (getHandle()) return true;
|
|
164
179
|
const sessionHandle = getSessionHandle();
|
|
165
180
|
if (sessionHandle) return true;
|
|
166
181
|
const config = load();
|
|
167
|
-
return config.handle && config.handle.length > 0;
|
|
182
|
+
return !!(config.handle && config.handle.length > 0);
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
// Session management - unique ID per Claude Code instance
|
|
@@ -302,10 +317,21 @@ function saveAuthToken(token) {
|
|
|
302
317
|
authMethod: 'github' // Track that this is GitHub OAuth
|
|
303
318
|
});
|
|
304
319
|
|
|
305
|
-
// Also save to shared config for persistence across MCP restarts
|
|
320
|
+
// Also save to shared config for persistence across MCP restarts.
|
|
321
|
+
//
|
|
322
|
+
// The handle rides WITH the token. Persisting a credential and no identity is how a
|
|
323
|
+
// config comes to hold a valid token under no name — and save() cannot repair it,
|
|
324
|
+
// because it writes `username: undefined` in that case and JSON.stringify drops the
|
|
325
|
+
// key rather than storing null. The state is invisible and permanent.
|
|
306
326
|
const cfg = load();
|
|
307
327
|
cfg.authToken = token;
|
|
308
328
|
cfg.authMethod = 'github';
|
|
329
|
+
if (!cfg.handle) {
|
|
330
|
+
// Derive it from the credential itself rather than leaving the file anonymous.
|
|
331
|
+
try {
|
|
332
|
+
cfg.handle = require('./auth-store').inspectToken(token).handle || cfg.handle;
|
|
333
|
+
} catch (e) { /* auth-store unavailable at early boot — the token still saves */ }
|
|
334
|
+
}
|
|
309
335
|
save(cfg);
|
|
310
336
|
}
|
|
311
337
|
|
package/index.js
CHANGED
|
@@ -99,6 +99,24 @@ function inferPromptFromArgs(toolName, args) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// TERMINAL ESCAPES ARE OPT-IN, and default to OFF.
|
|
103
|
+
//
|
|
104
|
+
// An MCP server CANNOT know what renders its output. These OSC sequences assume an
|
|
105
|
+
// iTerm-like terminal; every other client shows them as literal junk on the end of every
|
|
106
|
+
// tool result:
|
|
107
|
+
//
|
|
108
|
+
// ]0;vibe: 2 online · @pastelle]1337;SetBadgeFormat=4peL
|
|
109
|
+
//
|
|
110
|
+
// The comment that used to sit here claimed they were "invisible in the transcript".
|
|
111
|
+
// They are not — they were visible throughout a full working session in Claude Code, on
|
|
112
|
+
// every single vibe tool call. That is a claim about a state nobody verified, which is
|
|
113
|
+
// the exact defect class this codebase keeps paying for (canon law 2), and it is worse
|
|
114
|
+
// than usual here because the claim is what stopped anyone looking.
|
|
115
|
+
//
|
|
116
|
+
// Client-agnostic payloads are the rule. Someone on iTerm who wants a live title and
|
|
117
|
+
// badge can ask for it; nobody else should pay for it.
|
|
118
|
+
const TERM_ESCAPES = ['1', 'true'].includes(String(process.env.VIBE_TERM_ESCAPES || '').toLowerCase());
|
|
119
|
+
|
|
102
120
|
// Generate terminal title escape sequence (OSC 0)
|
|
103
121
|
function getTerminalTitle(onlineCount, unreadCount, lastActivity) {
|
|
104
122
|
const parts = [];
|
|
@@ -235,9 +253,10 @@ async function getPresenceFooter() {
|
|
|
235
253
|
|
|
236
254
|
// Terminal title + badge: ambient by nature, invisible in the transcript.
|
|
237
255
|
const lastActivity = others.length > 0 ? `@${others[0].handle}` : null;
|
|
238
|
-
const escapes =
|
|
239
|
-
getTerminalTitle(onlineCount, unreadCount, lastActivity) +
|
|
240
|
-
|
|
256
|
+
const escapes = TERM_ESCAPES
|
|
257
|
+
? getTerminalTitle(onlineCount, unreadCount, lastActivity) +
|
|
258
|
+
getBadgeSequence(onlineCount, unreadCount)
|
|
259
|
+
: '';
|
|
241
260
|
|
|
242
261
|
// The status line. Counts only — no mood inference, no nudges.
|
|
243
262
|
const parts = ['vibe'];
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slashvibe-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9",
|
|
4
4
|
"mcpName": "io.github.vibecodinginc/vibe",
|
|
5
5
|
"description": "Presence + messaging for terminal coding agents (Claude Code, Codex, Cursor) — the /vibe kernel",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"slashvibe-mcp": "
|
|
9
|
-
"vibe-setup": "
|
|
8
|
+
"slashvibe-mcp": "cli.js",
|
|
9
|
+
"vibe-setup": "setup.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "node index.js",
|
package/version.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.8.
|
|
3
|
-
"updated": "2026-08-
|
|
4
|
-
"changelog": "
|
|
2
|
+
"version": "0.8.9",
|
|
3
|
+
"updated": "2026-08-02",
|
|
4
|
+
"changelog": "One machine can hold more than one saved /vibe credential. Until now the first token on disk won even when it had expired, stranding a working 30-day login in the other file. /vibe now chooses a credential that is still locally usable, reports signed out when every saved credential is expired, and keeps terminal-specific title and badge escapes off unless they are explicitly enabled.",
|
|
5
5
|
"features": [
|
|
6
|
-
"A
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"One login lifetime — 30 days, including for brand-new accounts"
|
|
6
|
+
"A working saved credential wins over an expired credential in another config file",
|
|
7
|
+
"All expired credentials report signed out instead of impersonating a live session",
|
|
8
|
+
"Terminal title and badge escapes are opt-in with VIBE_TERM_ESCAPES=1"
|
|
10
9
|
],
|
|
11
10
|
"deprecated": [],
|
|
12
11
|
"breaking": false,
|