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 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
- // Cross-client: if no token from config.json, check terminal/buddy auth.json
68
- if (!_token) {
69
- const terminalAuth = config.loadTerminalAuth();
70
- if (terminalAuth?.token) _token = terminalAuth.token;
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
- // Check session first, then shared config
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
- getBadgeSequence(onlineCount, unreadCount);
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.7",
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": "./cli.js",
9
- "vibe-setup": "./setup.js"
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.7",
3
- "updated": "2026-08-01",
4
- "changelog": "Three ways /vibe told you something untrue, all on the path a new person walks. Installing failed with \"Could not reach slashvibe.dev\" whenever a cache was over quota — the site was serving fine, and a subsystem setup never uses was refusing you the product. Re-running setup on a machine whose login had expired answered \"/vibe is configured\" and quit, so the one command for fixing it said there was nothing to fix. And asking who was around while signed out reported an empty room instead of saying you were signed out, which is the worst thing this product can get wrong. Logins are now 30 days everywhere; new accounts had been getting one-hour ones.",
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 degraded cache no longer blocks installing",
7
- "An expired login says so, names the account, and signs you back in",
8
- "Signed out reads as signed out, not as an empty room",
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,