@askmesh/mcp 0.9.0 → 0.10.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/dist/index.d.ts +1 -1
- package/dist/index.js +56 -53
- package/dist/statusline/cache.d.ts +3 -0
- package/dist/statusline/cache.js +10 -1
- package/dist/tools/askmesh.d.ts +1 -0
- package/dist/tools/askmesh.js +110 -28
- package/package.json +2 -1
- package/statusline.sh +25 -18
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import 'dotenv/config';
|
package/dist/index.js
CHANGED
|
@@ -1,70 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
2
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
5
|
import { AskMeshClient } from './client/askmesh_client.js';
|
|
5
6
|
import { SseListener } from './sse/sse_listener.js';
|
|
6
7
|
import { AutoResponder } from './agent/auto_responder.js';
|
|
7
|
-
import { registerAskMesh } from './tools/askmesh.js';
|
|
8
|
+
import { registerAskMesh, registerSetupOnly } from './tools/askmesh.js';
|
|
8
9
|
import * as statusCache from './statusline/cache.js';
|
|
9
10
|
const TOKEN = process.env.ASKMESH_TOKEN;
|
|
10
11
|
const URL = process.env.ASKMESH_URL || 'https://api.askmesh.dev';
|
|
11
|
-
if (!TOKEN) {
|
|
12
|
-
console.error('[AskMesh] ASKMESH_TOKEN is required');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
const client = new AskMeshClient(URL, TOKEN);
|
|
16
|
-
const autoResponder = new AutoResponder(client);
|
|
17
12
|
const server = new McpServer({
|
|
18
13
|
name: 'askmesh',
|
|
19
|
-
version: '0.
|
|
14
|
+
version: '0.10.0',
|
|
20
15
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
statusCache.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
16
|
+
if (!TOKEN) {
|
|
17
|
+
// No token — start in setup-only mode
|
|
18
|
+
console.error('[AskMesh] No ASKMESH_TOKEN found. Starting in setup mode.');
|
|
19
|
+
registerSetupOnly(server);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Full mode
|
|
23
|
+
statusCache.init(TOKEN);
|
|
24
|
+
const client = new AskMeshClient(URL, TOKEN);
|
|
25
|
+
const autoResponder = new AutoResponder(client);
|
|
26
|
+
registerAskMesh(server, client);
|
|
27
|
+
autoResponder.setServer(server.server);
|
|
28
|
+
// Start SSE listener
|
|
29
|
+
const sse = new SseListener();
|
|
30
|
+
sse.start(URL, TOKEN, async (request) => {
|
|
31
|
+
statusCache.onRequestIncoming();
|
|
32
|
+
await autoResponder.handleRequest(request);
|
|
33
|
+
}, (answer) => {
|
|
34
|
+
console.error(`[AskMesh] Answer received for request #${answer.id}: "${answer.answer.slice(0, 100)}${answer.answer.length > 100 ? '...' : ''}"`);
|
|
35
|
+
}, (_reply) => {
|
|
36
|
+
statusCache.onReplyAdded();
|
|
37
|
+
}, (_closed) => {
|
|
38
|
+
statusCache.onThreadClosed();
|
|
39
|
+
});
|
|
40
|
+
// Polling fallback
|
|
41
|
+
const POLL_INTERVAL = Number(process.env.ASKMESH_POLL_INTERVAL || 0);
|
|
42
|
+
if (POLL_INTERVAL > 0) {
|
|
43
|
+
console.error(`[AskMesh] Polling enabled every ${POLL_INTERVAL}s`);
|
|
44
|
+
setInterval(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const { requests } = await client.getPendingRequests();
|
|
47
|
+
for (const req of requests) {
|
|
48
|
+
console.error(`[AskMesh] Polled pending #${req.id}: "${req.question}"`);
|
|
49
|
+
await autoResponder.handleRequest({
|
|
50
|
+
id: req.id,
|
|
51
|
+
fromAgentId: req.fromAgentId,
|
|
52
|
+
fromUsername: `agent#${req.fromAgentId}`,
|
|
53
|
+
question: req.question,
|
|
54
|
+
context: req.context,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
+
catch { }
|
|
59
|
+
}, POLL_INTERVAL * 1000);
|
|
60
|
+
}
|
|
61
|
+
// Cleanup on exit
|
|
62
|
+
process.on('SIGINT', () => {
|
|
63
|
+
sse.stop();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
});
|
|
66
|
+
process.on('SIGTERM', () => {
|
|
67
|
+
sse.stop();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
58
70
|
}
|
|
59
|
-
// Cleanup on exit
|
|
60
|
-
process.on('SIGINT', () => {
|
|
61
|
-
sse.stop();
|
|
62
|
-
process.exit(0);
|
|
63
|
-
});
|
|
64
|
-
process.on('SIGTERM', () => {
|
|
65
|
-
sse.stop();
|
|
66
|
-
process.exit(0);
|
|
67
|
-
});
|
|
68
71
|
// Start MCP server via stdio
|
|
69
72
|
const transport = new StdioServerTransport();
|
|
70
73
|
await server.connect(transport);
|
|
@@ -4,6 +4,9 @@ interface StatusCache {
|
|
|
4
4
|
unread_replies: number;
|
|
5
5
|
last_update: string;
|
|
6
6
|
}
|
|
7
|
+
/** Call once at startup with the agent token to isolate the cache file per agent */
|
|
8
|
+
export declare function init(token: string): void;
|
|
9
|
+
export declare function getCacheFile(): string;
|
|
7
10
|
export declare function load(): StatusCache;
|
|
8
11
|
export declare function onRequestIncoming(): void;
|
|
9
12
|
export declare function onReplyAdded(): void;
|
package/dist/statusline/cache.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
import { tmpdir } from 'os';
|
|
3
4
|
import { join } from 'path';
|
|
4
|
-
|
|
5
|
+
let CACHE_FILE = join(tmpdir(), 'askmesh_status.json');
|
|
5
6
|
let cache = { pending: 0, active: 0, unread_replies: 0, last_update: '' };
|
|
7
|
+
/** Call once at startup with the agent token to isolate the cache file per agent */
|
|
8
|
+
export function init(token) {
|
|
9
|
+
const hash = createHash('md5').update(token).digest('hex').slice(0, 8);
|
|
10
|
+
CACHE_FILE = join(tmpdir(), `askmesh_status_${hash}.json`);
|
|
11
|
+
}
|
|
6
12
|
function flush() {
|
|
7
13
|
cache.last_update = new Date().toISOString();
|
|
8
14
|
try {
|
|
@@ -10,6 +16,9 @@ function flush() {
|
|
|
10
16
|
}
|
|
11
17
|
catch { }
|
|
12
18
|
}
|
|
19
|
+
export function getCacheFile() {
|
|
20
|
+
return CACHE_FILE;
|
|
21
|
+
}
|
|
13
22
|
export function load() {
|
|
14
23
|
try {
|
|
15
24
|
const raw = readFileSync(CACHE_FILE, 'utf-8');
|
package/dist/tools/askmesh.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { AskMeshClient } from '../client/askmesh_client.js';
|
|
3
3
|
export declare function registerAskMesh(server: McpServer, client: AskMeshClient): void;
|
|
4
|
+
export declare function registerSetupOnly(server: McpServer): void;
|
package/dist/tools/askmesh.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { mkdirSync, writeFileSync, readFileSync, appendFileSync, existsSync } from 'fs';
|
|
2
|
+
import { mkdirSync, writeFileSync, readFileSync, appendFileSync, existsSync, unlinkSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
5
6
|
import * as statusCache from '../statusline/cache.js';
|
|
6
7
|
export function registerAskMesh(server, client) {
|
|
@@ -40,7 +41,8 @@ Actions disponibles :
|
|
|
40
41
|
message: z.string().optional().describe('Réponse ou contexte à envoyer (pour answer/reply/context)'),
|
|
41
42
|
parentThreadId: z.number().optional().describe('ID du thread parent (pour ask, lie les threads entre eux)'),
|
|
42
43
|
teamId: z.number().optional().describe('ID de la team (pour board)'),
|
|
43
|
-
|
|
44
|
+
token: z.string().optional().describe('API token AskMesh (pour setup — depuis askmesh.dev > Settings)'),
|
|
45
|
+
}, async ({ action, username, question, requestId, message, parentThreadId, teamId, token }) => {
|
|
44
46
|
switch (action) {
|
|
45
47
|
case 'ask': {
|
|
46
48
|
if (!username || !question) {
|
|
@@ -208,56 +210,109 @@ Actions disponibles :
|
|
|
208
210
|
return text(`Contexte mis à jour (${message.length} caractères).`);
|
|
209
211
|
}
|
|
210
212
|
case 'setup': {
|
|
211
|
-
return setupSkillsAndStatusLine();
|
|
213
|
+
return setupSkillsAndStatusLine(token);
|
|
212
214
|
}
|
|
213
215
|
default:
|
|
214
216
|
return text('Action inconnue. Actions disponibles : ask, list, status, pending, answer, context, setup');
|
|
215
217
|
}
|
|
216
218
|
});
|
|
217
219
|
}
|
|
220
|
+
export function registerSetupOnly(server) {
|
|
221
|
+
server.tool('askmesh', `AskMesh — première configuration requise.
|
|
222
|
+
Aucun token trouvé. Utilise cet outil pour configurer AskMesh dans ce projet.
|
|
223
|
+
|
|
224
|
+
Actions disponibles :
|
|
225
|
+
- "setup" : configurer le projet (crée le .env, installe les slash commands, configure la status line)
|
|
226
|
+
|
|
227
|
+
Pour commencer :
|
|
228
|
+
1. Inscris-toi sur https://askmesh.dev
|
|
229
|
+
2. Copie ton API token depuis Settings
|
|
230
|
+
3. Lance l'action "setup" avec ton token pour tout installer`, {
|
|
231
|
+
action: z.enum(['setup']).describe('Action à effectuer'),
|
|
232
|
+
token: z.string().optional().describe('Ton API token AskMesh (depuis askmesh.dev > Settings)'),
|
|
233
|
+
}, async ({ action, token }) => {
|
|
234
|
+
if (action === 'setup') {
|
|
235
|
+
return setupSkillsAndStatusLine(token);
|
|
236
|
+
}
|
|
237
|
+
return text('Action inconnue.');
|
|
238
|
+
});
|
|
239
|
+
}
|
|
218
240
|
function text(t) {
|
|
219
241
|
return { content: [{ type: 'text', text: t }] };
|
|
220
242
|
}
|
|
221
|
-
function setupSkillsAndStatusLine() {
|
|
243
|
+
function setupSkillsAndStatusLine(token) {
|
|
222
244
|
const cwd = process.cwd();
|
|
223
245
|
const commandsDir = join(cwd, '.claude', 'commands');
|
|
224
246
|
const installed = [];
|
|
225
247
|
const skipped = [];
|
|
226
248
|
// Skills definitions
|
|
227
249
|
const skills = {
|
|
228
|
-
'inbox.md': `Utilise l'outil MCP askmesh avec l'action "pending" pour vérifier les messages reçus en attente, puis avec l'action "inbox" pour voir les réponses à mes messages envoyés.\n\nAffiche un résumé clair :\n1. D'abord les messages reçus à traiter (pending)\n2. Ensuite les réponses reçues (inbox)\n\nSi tout est vide, dis simplement "Aucun message en attente."`,
|
|
229
|
-
'broadcast.md': `Utilise l'outil MCP askmesh avec l'action "broadcast" pour envoyer un message à toute l'équipe.\n\nLe message à diffuser : $ARGUMENTS\n\nSi aucun message n'est fourni, demande à l'utilisateur quel message il souhaite broadcaster.`,
|
|
230
|
-
'reply.md': `Utilise l'outil MCP askmesh pour répondre à un thread.\n\nArguments attendus : $ARGUMENTS (format: <requestId> <message>)\n\nExemple : /reply 42 Voici ma réponse détaillée...\n\nSi le requestId n'est pas fourni, utilise d'abord l'action "pending" pour lister les threads en attente et demande à l'utilisateur lequel traiter.\n\nUtilise l'action "reply" avec le requestId et le message.`,
|
|
231
|
-
'board.md': `Utilise l'outil MCP askmesh avec l'action "board" pour afficher le tableau kanban de l'équipe.\n\nSi un teamId est fourni dans les arguments ($ARGUMENTS), utilise-le directement.\nSinon, utilise d'abord l'action "list" pour identifier les teams disponibles, puis affiche le board de la première team.\n\nPrésente le résultat de manière claire avec les colonnes : Pending, In Progress, Active, Done.`,
|
|
232
|
-
'threads.md': `Utilise l'outil MCP askmesh avec l'action "my-threads" pour afficher toutes mes conversations actives.\n\nPrésente les threads regroupés par statut :\n- En attente (pending)\n- En cours (in_progress)\n- Actifs (active)\n\nIgnore les threads clos. Pour chaque thread, montre : l'ID, qui a posé la question, le sujet, et le nombre de réponses.`,
|
|
233
|
-
'
|
|
250
|
+
'ask-inbox.md': `Utilise l'outil MCP askmesh avec l'action "pending" pour vérifier les messages reçus en attente, puis avec l'action "inbox" pour voir les réponses à mes messages envoyés.\n\nAffiche un résumé clair :\n1. D'abord les messages reçus à traiter (pending)\n2. Ensuite les réponses reçues (inbox)\n\nSi tout est vide, dis simplement "Aucun message en attente."`,
|
|
251
|
+
'ask-broadcast.md': `Utilise l'outil MCP askmesh avec l'action "broadcast" pour envoyer un message à toute l'équipe.\n\nLe message à diffuser : $ARGUMENTS\n\nSi aucun message n'est fourni, demande à l'utilisateur quel message il souhaite broadcaster.`,
|
|
252
|
+
'ask-reply.md': `Utilise l'outil MCP askmesh pour répondre à un thread.\n\nArguments attendus : $ARGUMENTS (format: <requestId> <message>)\n\nExemple : /ask-reply 42 Voici ma réponse détaillée...\n\nSi le requestId n'est pas fourni, utilise d'abord l'action "pending" pour lister les threads en attente et demande à l'utilisateur lequel traiter.\n\nUtilise l'action "reply" avec le requestId et le message.`,
|
|
253
|
+
'ask-board.md': `Utilise l'outil MCP askmesh avec l'action "board" pour afficher le tableau kanban de l'équipe.\n\nSi un teamId est fourni dans les arguments ($ARGUMENTS), utilise-le directement.\nSinon, utilise d'abord l'action "list" pour identifier les teams disponibles, puis affiche le board de la première team.\n\nPrésente le résultat de manière claire avec les colonnes : Pending, In Progress, Active, Done.`,
|
|
254
|
+
'ask-threads.md': `Utilise l'outil MCP askmesh avec l'action "my-threads" pour afficher toutes mes conversations actives.\n\nPrésente les threads regroupés par statut :\n- En attente (pending)\n- En cours (in_progress)\n- Actifs (active)\n\nIgnore les threads clos. Pour chaque thread, montre : l'ID, qui a posé la question, le sujet, et le nombre de réponses.`,
|
|
255
|
+
'ask-status.md': `Utilise l'outil MCP askmesh avec l'action "list" pour voir qui est connecté sur le réseau.\n\nAffiche la liste des membres de l'équipe avec leur statut (online/offline) de manière claire et concise.`,
|
|
256
|
+
'ask-setup.md': `Utilise l'outil MCP askmesh avec l'action "setup" pour installer ou mettre à jour la configuration AskMesh dans ce projet.\n\nCela va :\n- Créer ou vérifier le .env avec le token AskMesh\n- Installer les slash commands (/ask-inbox, /ask-broadcast, etc.)\n- Configurer la status line\n- Ajouter .env au .gitignore\n\nSi l'utilisateur fournit un token dans les arguments ($ARGUMENTS), passe-le en paramètre "token".`,
|
|
234
257
|
};
|
|
235
258
|
// Create commands directory
|
|
236
259
|
try {
|
|
237
260
|
mkdirSync(commandsDir, { recursive: true });
|
|
238
261
|
}
|
|
239
262
|
catch { }
|
|
240
|
-
// Write skills
|
|
263
|
+
// Write skills (always overwrite to keep them up to date)
|
|
241
264
|
for (const [filename, content] of Object.entries(skills)) {
|
|
242
265
|
const filepath = join(commandsDir, filename);
|
|
243
|
-
|
|
266
|
+
const existed = existsSync(filepath);
|
|
267
|
+
writeFileSync(filepath, content);
|
|
268
|
+
if (existed) {
|
|
244
269
|
skipped.push(filename);
|
|
245
270
|
}
|
|
246
271
|
else {
|
|
247
|
-
writeFileSync(filepath, content);
|
|
248
272
|
installed.push(filename);
|
|
249
273
|
}
|
|
250
274
|
}
|
|
251
|
-
//
|
|
275
|
+
// Remove old skill names (migration from pre-0.10)
|
|
276
|
+
const oldSkills = ['inbox.md', 'broadcast.md', 'reply.md', 'board.md', 'threads.md', 'mesh-status.md'];
|
|
277
|
+
const removed = [];
|
|
278
|
+
for (const old of oldSkills) {
|
|
279
|
+
const oldPath = join(commandsDir, old);
|
|
280
|
+
if (existsSync(oldPath)) {
|
|
281
|
+
try {
|
|
282
|
+
unlinkSync(oldPath);
|
|
283
|
+
removed.push(old);
|
|
284
|
+
}
|
|
285
|
+
catch { }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Handle .env token
|
|
252
289
|
const envPath = join(cwd, '.env');
|
|
253
290
|
let envStatus = '';
|
|
254
|
-
if (
|
|
291
|
+
if (token) {
|
|
292
|
+
if (existsSync(envPath)) {
|
|
293
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
294
|
+
if (content.includes('ASKMESH_TOKEN')) {
|
|
295
|
+
const updated = content.replace(/ASKMESH_TOKEN=.*/, `ASKMESH_TOKEN=${token}`);
|
|
296
|
+
writeFileSync(envPath, updated);
|
|
297
|
+
envStatus = '.env : ASKMESH_TOKEN mis à jour';
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
appendFileSync(envPath, `\nASKMESH_TOKEN=${token}\nASKMESH_URL=https://api.askmesh.dev\n`);
|
|
301
|
+
envStatus = '.env : ASKMESH_TOKEN ajouté';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
writeFileSync(envPath, `ASKMESH_TOKEN=${token}\nASKMESH_URL=https://api.askmesh.dev\n`);
|
|
306
|
+
envStatus = '.env créé avec le token';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else if (existsSync(envPath)) {
|
|
255
310
|
const envContent = readFileSync(envPath, 'utf-8');
|
|
256
|
-
if (envContent.includes('ASKMESH_TOKEN')) {
|
|
257
|
-
envStatus = '.env : ASKMESH_TOKEN
|
|
311
|
+
if (envContent.includes('ASKMESH_TOKEN') && !envContent.includes('your_token_here')) {
|
|
312
|
+
envStatus = '.env : ASKMESH_TOKEN présent';
|
|
258
313
|
}
|
|
259
314
|
else {
|
|
260
|
-
envStatus = '.env : ASKMESH_TOKEN absent — ajoute-le
|
|
315
|
+
envStatus = '.env : ASKMESH_TOKEN absent — ajoute-le ou relance /ask-setup avec ton token';
|
|
261
316
|
}
|
|
262
317
|
}
|
|
263
318
|
else {
|
|
@@ -274,29 +329,56 @@ function setupSkillsAndStatusLine() {
|
|
|
274
329
|
gitignoreStatus = '.gitignore : .env ajouté';
|
|
275
330
|
}
|
|
276
331
|
}
|
|
277
|
-
//
|
|
332
|
+
// Auto-configure status line in ~/.claude/settings.json
|
|
278
333
|
const mcpDir = dirname(fileURLToPath(import.meta.url));
|
|
279
334
|
const statusLineScript = join(mcpDir, '..', 'statusline.sh');
|
|
280
335
|
const statusLineExists = existsSync(statusLineScript);
|
|
336
|
+
let statusLineStatus = '';
|
|
337
|
+
if (statusLineExists) {
|
|
338
|
+
const claudeSettingsDir = join(homedir(), '.claude');
|
|
339
|
+
const claudeSettingsPath = join(claudeSettingsDir, 'settings.json');
|
|
340
|
+
try {
|
|
341
|
+
mkdirSync(claudeSettingsDir, { recursive: true });
|
|
342
|
+
let settings = {};
|
|
343
|
+
if (existsSync(claudeSettingsPath)) {
|
|
344
|
+
settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8'));
|
|
345
|
+
}
|
|
346
|
+
const currentCommand = settings.statusLine?.command;
|
|
347
|
+
if (currentCommand === statusLineScript) {
|
|
348
|
+
statusLineStatus = 'Status line : déjà configurée';
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
settings.statusLine = { type: 'command', command: statusLineScript };
|
|
352
|
+
writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
353
|
+
statusLineStatus = 'Status line : configurée automatiquement';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
statusLineStatus = `Status line : erreur de configuration — ${e}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
statusLineStatus = 'Status line : script non trouvé (sera disponible après npm install)';
|
|
362
|
+
}
|
|
363
|
+
// Build report
|
|
281
364
|
const lines = ['Setup AskMesh terminé !\n'];
|
|
282
365
|
if (installed.length > 0) {
|
|
283
366
|
lines.push(`Slash commands installées : ${installed.map((f) => '/' + f.replace('.md', '')).join(', ')}`);
|
|
284
367
|
}
|
|
285
368
|
if (skipped.length > 0) {
|
|
286
|
-
lines.push(`
|
|
369
|
+
lines.push(`Mises à jour : ${skipped.map((f) => '/' + f.replace('.md', '')).join(', ')}`);
|
|
370
|
+
}
|
|
371
|
+
if (removed.length > 0) {
|
|
372
|
+
lines.push(`Anciennes skills supprimées : ${removed.map((f) => '/' + f.replace('.md', '')).join(', ')}`);
|
|
287
373
|
}
|
|
288
374
|
lines.push('');
|
|
289
375
|
lines.push(envStatus);
|
|
290
376
|
if (gitignoreStatus)
|
|
291
377
|
lines.push(gitignoreStatus);
|
|
292
|
-
lines.push(
|
|
293
|
-
if (
|
|
294
|
-
lines.push(
|
|
295
|
-
lines.push(
|
|
296
|
-
lines.push(` "statusLine": { "type": "command", "command": "${statusLineScript}" }`);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
lines.push('Status line script non trouvé (sera disponible après npm install)');
|
|
378
|
+
lines.push(statusLineStatus);
|
|
379
|
+
if (token) {
|
|
380
|
+
lines.push('');
|
|
381
|
+
lines.push('Redémarre Claude Code pour activer AskMesh complètement.');
|
|
300
382
|
}
|
|
301
383
|
return text(lines.join('\n'));
|
|
302
384
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askmesh/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
37
|
+
"dotenv": "^17.4.0",
|
|
37
38
|
"eventsource": "^2.0.2",
|
|
38
39
|
"zod": "^4.3.6"
|
|
39
40
|
},
|
package/statusline.sh
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# AskMesh status line for Claude Code
|
|
3
|
-
# Reads cached notification counts from
|
|
4
|
-
#
|
|
3
|
+
# Reads cached notification counts from MCP server(s)
|
|
4
|
+
# Aggregates across all active agent caches
|
|
5
5
|
|
|
6
6
|
input=$(cat)
|
|
7
|
-
model=$(echo "$input" | jq -r '.model.display_name // "Claude"' 2>/dev/null)
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
tmpdir="${TMPDIR:-/tmp}"
|
|
9
|
+
pending=0
|
|
10
|
+
active=0
|
|
11
|
+
replies=0
|
|
10
12
|
|
|
11
|
-
|
|
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
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
pending=$(jq -r '.pending // 0' "$cache_file" 2>/dev/null)
|
|
15
|
-
active=$(jq -r '.active // 0' "$cache_file" 2>/dev/null)
|
|
16
|
-
replies=$(jq -r '.unread_replies // 0' "$cache_file" 2>/dev/null)
|
|
24
|
+
parts=()
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
fi
|
|
26
|
+
if [ "$pending" -gt 0 ] 2>/dev/null; then
|
|
27
|
+
parts+=("\033[33m${pending}↓\033[0m")
|
|
28
|
+
fi
|
|
29
|
+
if [ "$active" -gt 0 ] 2>/dev/null; then
|
|
30
|
+
parts+=("\033[32m${active}~\033[0m")
|
|
31
|
+
fi
|
|
32
|
+
if [ "$replies" -gt 0 ] 2>/dev/null; then
|
|
33
|
+
parts+=("\033[36m${replies}>\033[0m")
|
|
27
34
|
fi
|
|
28
35
|
|
|
29
36
|
if [ ${#parts[@]} -gt 0 ]; then
|