@askmesh/mcp 0.10.4 → 0.10.5

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/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const TOKEN = process.env.ASKMESH_TOKEN;
11
11
  const URL = process.env.ASKMESH_URL || 'https://api.askmesh.dev';
12
12
  const server = new McpServer({
13
13
  name: 'askmesh',
14
- version: '0.10.4',
14
+ version: '0.10.5',
15
15
  });
16
16
  if (!TOKEN) {
17
17
  // No token — start in setup-only mode
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { mkdirSync, writeFileSync, readFileSync, appendFileSync, existsSync, unlinkSync, copyFileSync, chmodSync } from 'fs';
3
3
  import { join, dirname } from 'path';
4
4
  import { homedir } from 'os';
5
+ import { createHash } from 'crypto';
5
6
  import { fileURLToPath } from 'url';
6
7
  import * as statusCache from '../statusline/cache.js';
7
8
  export function registerAskMesh(server, client) {
@@ -107,23 +108,39 @@ Actions disponibles :
107
108
  const { requests } = await client.getSentRequests();
108
109
  if (requests.length === 0)
109
110
  return text('Aucune question envoyée.');
110
- const lines = requests.map((r) => {
111
- const icon = r.status === 'closed' ? '✅' : r.status === 'active' ? '💬' : r.status === 'answered' ? '✅' : '⏳';
112
- let line = `${icon} #${r.id} @${r.toUsername}: "${r.question}" [${r.status}]`;
113
- if (r.answer)
114
- line += `\n Réponse: ${r.answer}`;
115
- return line;
116
- });
117
- return text(`Questions envoyées:\n\n${lines.join('\n\n')}`);
111
+ // Separate threads with unread replies from the rest
112
+ const withReply = requests.filter((r) => r.status === 'active' || r.status === 'answered');
113
+ const pending = requests.filter((r) => r.status === 'pending' || r.status === 'in_progress');
114
+ const closed = requests.filter((r) => r.status === 'closed');
115
+ const sections = [];
116
+ if (withReply.length > 0) {
117
+ const lines = withReply.map((r) => {
118
+ let line = `🔔 #${r.id} → @${r.toUsername}: "${r.question}"`;
119
+ if (r.answer)
120
+ line += `\n Réponse: ${r.answer}`;
121
+ line += `\n → Utilise action "thread" requestId ${r.id} pour voir le fil complet, ou "close" pour clôturer.`;
122
+ return line;
123
+ });
124
+ sections.push(`Réponses à lire (${withReply.length}):\n\n${lines.join('\n\n')}`);
125
+ }
126
+ if (pending.length > 0) {
127
+ const lines = pending.map((r) => `⏳ #${r.id} → @${r.toUsername}: "${r.question}" [${r.status}]`);
128
+ sections.push(`En attente de réponse (${pending.length}):\n${lines.join('\n')}`);
129
+ }
130
+ if (closed.length > 0) {
131
+ const lines = closed.map((r) => `✅ #${r.id} → @${r.toUsername}: "${r.question}"`);
132
+ sections.push(`Terminés (${closed.length}):\n${lines.join('\n')}`);
133
+ }
134
+ if (sections.length === 0)
135
+ return text('Rien à traiter, tout est à jour.');
136
+ return text(sections.join('\n\n---\n\n'));
118
137
  }
119
138
  case 'answer': {
120
139
  if (!requestId || !message) {
121
140
  return text("Paramètres requis : requestId et message");
122
141
  }
123
142
  const result = await client.answerRequest(requestId, message);
124
- // Auto-close thread after answering
125
- await client.closeThread(requestId).catch(() => { });
126
- return text(`Réponse envoyée et thread #${result.id} clôturé.`);
143
+ return text(`Réponse envoyée au thread #${result.id}. Le demandeur peut consulter la réponse via /ask-inbox.`);
127
144
  }
128
145
  case 'reply': {
129
146
  if (!requestId || !message) {
@@ -247,7 +264,7 @@ Si l'utilisateur n'a pas encore de compte, dirige-le vers https://askmesh.dev po
247
264
  return text('Action inconnue.');
248
265
  });
249
266
  }
250
- const CURRENT_VERSION = '0.10.4';
267
+ const CURRENT_VERSION = '0.10.5';
251
268
  async function checkUpdateAndSetup() {
252
269
  const lines = [];
253
270
  lines.push(`Version actuelle : ${CURRENT_VERSION}`);
@@ -279,6 +296,14 @@ async function checkUpdateAndSetup() {
279
296
  function text(t) {
280
297
  return { content: [{ type: 'text', text: t }] };
281
298
  }
299
+ function readEnvToken(envPath) {
300
+ if (!existsSync(envPath))
301
+ return undefined;
302
+ const content = readFileSync(envPath, 'utf-8');
303
+ const match = content.match(/ASKMESH_TOKEN=(.+)/);
304
+ const val = match?.[1]?.trim();
305
+ return val && val !== 'your_token_here' ? val : undefined;
306
+ }
282
307
  function setupSkillsAndStatusLine(token, pollInterval, url) {
283
308
  const cwd = process.cwd();
284
309
  const commandsDir = join(cwd, '.claude', 'commands');
@@ -379,33 +404,49 @@ function setupSkillsAndStatusLine(token, pollInterval, url) {
379
404
  gitignoreStatus = '.gitignore : .env ajouté';
380
405
  }
381
406
  }
382
- // Auto-configure status line
383
- // Copy statusline.sh to a stable location (~/.claude/) so it survives npx cache clears
407
+ // Auto-configure status line — per-project, not global
408
+ // Copy statusline.sh to ~/.claude/ (stable location), configure per-project with agent hash
384
409
  const mcpDir = dirname(fileURLToPath(import.meta.url));
385
410
  const sourceScript = join(mcpDir, '..', '..', 'statusline.sh');
386
- const claudeSettingsDir = join(homedir(), '.claude');
387
- const stableScript = join(claudeSettingsDir, 'askmesh-statusline.sh');
388
- const claudeSettingsPath = join(claudeSettingsDir, 'settings.json');
411
+ const globalClaudeDir = join(homedir(), '.claude');
412
+ const stableScript = join(globalClaudeDir, 'askmesh-statusline.sh');
413
+ const projectClaudeDir = join(cwd, '.claude');
414
+ const projectSettingsPath = join(projectClaudeDir, 'settings.local.json');
389
415
  let statusLineStatus = '';
416
+ // Compute token hash for this agent
417
+ const envToken = token || readEnvToken(envPath);
418
+ const tokenHash = envToken ? createHash('md5').update(envToken).digest('hex').slice(0, 8) : '';
419
+ const statusLineCommand = tokenHash
420
+ ? `${stableScript} ${tokenHash}`
421
+ : stableScript;
390
422
  try {
391
- mkdirSync(claudeSettingsDir, { recursive: true });
423
+ mkdirSync(globalClaudeDir, { recursive: true });
424
+ mkdirSync(projectClaudeDir, { recursive: true });
392
425
  // Copy script to stable location
393
426
  if (existsSync(sourceScript)) {
394
427
  copyFileSync(sourceScript, stableScript);
395
428
  chmodSync(stableScript, 0o755);
396
429
  }
397
430
  if (existsSync(stableScript)) {
431
+ // Write to project-level settings (not global)
398
432
  let settings = {};
399
- if (existsSync(claudeSettingsPath)) {
400
- settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8'));
401
- }
402
- if (settings.statusLine?.command === stableScript) {
403
- statusLineStatus = 'Status line : déjà configurée';
433
+ if (existsSync(projectSettingsPath)) {
434
+ settings = JSON.parse(readFileSync(projectSettingsPath, 'utf-8'));
404
435
  }
405
- else {
406
- settings.statusLine = { type: 'command', command: stableScript };
407
- writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
408
- statusLineStatus = 'Status line : configurée automatiquement';
436
+ settings.statusLine = { type: 'command', command: statusLineCommand };
437
+ writeFileSync(projectSettingsPath, JSON.stringify(settings, null, 2) + '\n');
438
+ statusLineStatus = `Status line : configurée pour ce projet${tokenHash ? ` (agent ${tokenHash})` : ''}`;
439
+ // Clean up global settings if it had the old config
440
+ const globalSettingsPath = join(globalClaudeDir, 'settings.json');
441
+ if (existsSync(globalSettingsPath)) {
442
+ try {
443
+ const globalSettings = JSON.parse(readFileSync(globalSettingsPath, 'utf-8'));
444
+ if (globalSettings.statusLine?.command?.includes('askmesh')) {
445
+ delete globalSettings.statusLine;
446
+ writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2) + '\n');
447
+ }
448
+ }
449
+ catch { }
409
450
  }
410
451
  }
411
452
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askmesh/mcp",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
5
5
  "type": "module",
6
6
  "bin": {
package/statusline.sh CHANGED
@@ -1,25 +1,37 @@
1
1
  #!/bin/bash
2
2
  # AskMesh status line for Claude Code
3
- # Reads cached notification counts from MCP server(s)
4
- # Aggregates across all active agent caches
3
+ # Usage: statusline.sh [token_hash]
4
+ # If token_hash provided, reads only that agent's cache
5
+ # Otherwise reads the single cache file
5
6
 
6
7
  input=$(cat)
7
8
 
8
9
  tmpdir="${TMPDIR:-/tmp}"
10
+ hash="$1"
9
11
  pending=0
10
12
  active=0
11
13
  replies=0
12
14
 
13
- # Aggregate all agent cache files
14
- for f in "$tmpdir"/askmesh_status_*.json; do
15
- [ -f "$f" ] || continue
16
- p=$(jq -r '.pending // 0' "$f" 2>/dev/null)
17
- a=$(jq -r '.active // 0' "$f" 2>/dev/null)
18
- r=$(jq -r '.unread_replies // 0' "$f" 2>/dev/null)
19
- pending=$((pending + p))
20
- active=$((active + a))
21
- replies=$((replies + r))
22
- done
15
+ if [ -n "$hash" ]; then
16
+ # Read specific agent cache
17
+ f="$tmpdir/askmesh_status_${hash}.json"
18
+ if [ -f "$f" ]; then
19
+ pending=$(jq -r '.pending // 0' "$f" 2>/dev/null)
20
+ active=$(jq -r '.active // 0' "$f" 2>/dev/null)
21
+ replies=$(jq -r '.unread_replies // 0' "$f" 2>/dev/null)
22
+ fi
23
+ else
24
+ # Fallback: aggregate all agent caches
25
+ for f in "$tmpdir"/askmesh_status_*.json; do
26
+ [ -f "$f" ] || continue
27
+ p=$(jq -r '.pending // 0' "$f" 2>/dev/null)
28
+ a=$(jq -r '.active // 0' "$f" 2>/dev/null)
29
+ r=$(jq -r '.unread_replies // 0' "$f" 2>/dev/null)
30
+ pending=$((pending + p))
31
+ active=$((active + a))
32
+ replies=$((replies + r))
33
+ done
34
+ fi
23
35
 
24
36
  parts=()
25
37