@renatocostaguedesdemorais/devs-loop-mcp 0.1.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/.env.example +10 -0
- package/README.md +166 -0
- package/cli.cjs +640 -0
- package/config.json +101 -0
- package/devs-loop.md +677 -0
- package/lib/api.cjs +87 -0
- package/lib/coach.cjs +222 -0
- package/lib/config.cjs +104 -0
- package/lib/learnings.cjs +215 -0
- package/lib/listResolver.cjs +370 -0
- package/lib/paths.cjs +37 -0
- package/lib/progress.cjs +157 -0
- package/lib/session.cjs +220 -0
- package/lib/task.cjs +365 -0
- package/mcp-server.cjs +464 -0
- package/package.json +44 -0
package/mcp-server.cjs
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DEVS-LOOP MCP SERVER
|
|
4
|
+
// Exposes the devs-loop core as a personal MCP server over stdio
|
|
5
|
+
// ============================================================
|
|
6
|
+
|
|
7
|
+
const util = require("util");
|
|
8
|
+
const { z } = require("zod");
|
|
9
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
10
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
11
|
+
|
|
12
|
+
const config = require("./lib/config.cjs");
|
|
13
|
+
const learnings = require("./lib/learnings.cjs");
|
|
14
|
+
const listResolver = require("./lib/listResolver.cjs");
|
|
15
|
+
const progress = require("./lib/progress.cjs");
|
|
16
|
+
const session = require("./lib/session.cjs");
|
|
17
|
+
const task = require("./lib/task.cjs");
|
|
18
|
+
|
|
19
|
+
const originalConsoleLog = console.log;
|
|
20
|
+
const originalConsoleError = console.error;
|
|
21
|
+
|
|
22
|
+
function writeStderr(prefix, args) {
|
|
23
|
+
const message = util.format(...args);
|
|
24
|
+
process.stderr.write(`${prefix}${message}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log = (...args) => writeStderr("[devs-loop] ", args);
|
|
28
|
+
console.error = (...args) => writeStderr("[devs-loop:error] ", args);
|
|
29
|
+
|
|
30
|
+
function asText(value) {
|
|
31
|
+
if (value == null) return "";
|
|
32
|
+
if (typeof value === "string") return value;
|
|
33
|
+
return JSON.stringify(value, null, 2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createResponse(summary, data) {
|
|
37
|
+
const text = typeof summary === "string" ? summary : asText(summary);
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
structuredContent: data ?? (typeof summary === "string" ? undefined : summary),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runCaptured(fn) {
|
|
50
|
+
const logs = [];
|
|
51
|
+
const prevLog = console.log;
|
|
52
|
+
const prevError = console.error;
|
|
53
|
+
|
|
54
|
+
console.log = (...args) => logs.push({ level: "info", message: util.format(...args) });
|
|
55
|
+
console.error = (...args) => logs.push({ level: "error", message: util.format(...args) });
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await fn();
|
|
59
|
+
return { result, logs };
|
|
60
|
+
} finally {
|
|
61
|
+
console.log = prevLog;
|
|
62
|
+
console.error = prevError;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function withToolCapture(fn) {
|
|
67
|
+
try {
|
|
68
|
+
const { result, logs } = await runCaptured(fn);
|
|
69
|
+
return { ok: true, result, logs };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: error instanceof Error ? error.message : String(error),
|
|
74
|
+
logs: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function mergeTextAndLogs(summary, logs = []) {
|
|
80
|
+
if (!logs || logs.length === 0) return summary;
|
|
81
|
+
return `${summary}\n\nLogs:\n${logs.map((entry) => `- [${entry.level}] ${entry.message}`).join("\n")}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const server = new McpServer({
|
|
85
|
+
name: "devs-loop-mcp",
|
|
86
|
+
version: "1.0.0",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"devs_loop_projects",
|
|
91
|
+
{
|
|
92
|
+
title: "Listar projetos do devs-loop",
|
|
93
|
+
description: "Lista os projetos conhecidos no config do devs-loop.",
|
|
94
|
+
inputSchema: {},
|
|
95
|
+
},
|
|
96
|
+
async () => {
|
|
97
|
+
const projects = config.listProjects();
|
|
98
|
+
return createResponse(
|
|
99
|
+
`Projetos disponíveis:\n${projects.map((project) => `- ${project}`).join("\n")}`,
|
|
100
|
+
{ projects }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
server.registerTool(
|
|
106
|
+
"devs_loop_suggest_list",
|
|
107
|
+
{
|
|
108
|
+
title: "Sugerir lista do ClickUp",
|
|
109
|
+
description: "Mapeia as listas reais do ClickUp e sugere as melhores opções para um projeto.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
project: z.string(),
|
|
112
|
+
limit: z.number().int().positive().max(10).optional(),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
async ({ project, limit = 5 }) => {
|
|
116
|
+
const candidates = await listResolver.buildCandidates(project);
|
|
117
|
+
const recommended = listResolver.pickRecommended(candidates);
|
|
118
|
+
const output = {
|
|
119
|
+
project,
|
|
120
|
+
recommended: recommended
|
|
121
|
+
? {
|
|
122
|
+
id: recommended.id,
|
|
123
|
+
path: listResolver.formatListPath(recommended),
|
|
124
|
+
score: recommended.score,
|
|
125
|
+
reasons: recommended.reasons,
|
|
126
|
+
source: recommended.source,
|
|
127
|
+
}
|
|
128
|
+
: null,
|
|
129
|
+
candidates: candidates.slice(0, limit).map((candidate) => ({
|
|
130
|
+
id: candidate.id,
|
|
131
|
+
path: listResolver.formatListPath(candidate),
|
|
132
|
+
score: candidate.score,
|
|
133
|
+
reasons: candidate.reasons,
|
|
134
|
+
source: candidate.source,
|
|
135
|
+
})),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const lines = [];
|
|
139
|
+
if (output.recommended) {
|
|
140
|
+
lines.push(`Sugestao: ${output.recommended.path} (${output.recommended.id})`);
|
|
141
|
+
if (output.recommended.reasons?.length) {
|
|
142
|
+
lines.push(`Motivos: ${output.recommended.reasons.join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
lines.push(`Nenhuma sugestao forte encontrada para ${project}.`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (output.candidates.length > 0) {
|
|
149
|
+
lines.push("");
|
|
150
|
+
lines.push("Candidatas:");
|
|
151
|
+
for (const candidate of output.candidates) {
|
|
152
|
+
lines.push(`- ${candidate.path} (${candidate.id})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return createResponse(lines.join("\n"), output);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
server.registerTool(
|
|
161
|
+
"devs_loop_recent_progress",
|
|
162
|
+
{
|
|
163
|
+
title: "Consultar progresso recente",
|
|
164
|
+
description: "Lê o devs-loop-progress.md e devolve contexto recente do mesmo projeto ou iniciativa.",
|
|
165
|
+
inputSchema: {
|
|
166
|
+
project: z.string(),
|
|
167
|
+
initiative: z.string().optional(),
|
|
168
|
+
limit: z.number().int().positive().max(10).optional(),
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
async ({ project, initiative, limit = 2 }) => {
|
|
172
|
+
const recent = progress.loadRecentSessions(project, initiative, limit);
|
|
173
|
+
const formatted = progress.formatRecentSessions(recent) || `Nenhum progresso recente encontrado para ${project}.`;
|
|
174
|
+
return createResponse(formatted, { project, initiative, sessions: recent });
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
server.registerTool(
|
|
179
|
+
"devs_loop_init_session",
|
|
180
|
+
{
|
|
181
|
+
title: "Iniciar sessão do devs-loop",
|
|
182
|
+
description: "Inicia uma sessão local do devs-loop com projeto, iniciativa e lista do ClickUp.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
project: z.string(),
|
|
185
|
+
initiative: z.string(),
|
|
186
|
+
listId: z.string().optional(),
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
async ({ project, initiative, listId }) => {
|
|
190
|
+
const resolution = await listResolver.resolveList({
|
|
191
|
+
project,
|
|
192
|
+
explicitListId: listId,
|
|
193
|
+
allowPrompt: false,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const projectId = config.resolveProject(project);
|
|
197
|
+
if (!projectId) {
|
|
198
|
+
throw new Error(`Projeto '${project}' nao encontrado no config do devs-loop.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const current = session.init({
|
|
202
|
+
project,
|
|
203
|
+
projectId,
|
|
204
|
+
initiative,
|
|
205
|
+
listId: resolution.listId,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const recent = progress.loadRecentSessions(project, initiative, 2);
|
|
209
|
+
const recentText = progress.formatRecentSessions(recent);
|
|
210
|
+
const summary = [
|
|
211
|
+
`Sessao iniciada para ${project}.`,
|
|
212
|
+
`Iniciativa: ${current.initiative}`,
|
|
213
|
+
resolution.list ? `Lista: ${listResolver.formatListPath(resolution.list)} (${resolution.listId})` : `Lista: ${resolution.listId}`,
|
|
214
|
+
recentText ? `\n${recentText}` : "",
|
|
215
|
+
]
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
.join("\n");
|
|
218
|
+
|
|
219
|
+
return createResponse(summary, {
|
|
220
|
+
project,
|
|
221
|
+
initiative: current.initiative,
|
|
222
|
+
listId: resolution.listId,
|
|
223
|
+
listPath: resolution.list ? listResolver.formatListPath(resolution.list) : null,
|
|
224
|
+
recent,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
server.registerTool(
|
|
230
|
+
"devs_loop_create_task",
|
|
231
|
+
{
|
|
232
|
+
title: "Criar task no ClickUp via devs-loop",
|
|
233
|
+
description: "Cria uma task usando as regras do devs-loop e inicia timer por padrão.",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
name: z.string(),
|
|
236
|
+
project: z.string().optional(),
|
|
237
|
+
type: z.enum(["Feature", "Bug", "Infra", "QA", "Refactor", "Doc", "Spike"]).optional(),
|
|
238
|
+
size: z.enum(["P", "M", "G"]).optional(),
|
|
239
|
+
description: z.string().optional(),
|
|
240
|
+
checklist: z.array(z.string()).optional(),
|
|
241
|
+
listId: z.string().optional(),
|
|
242
|
+
parent: z.string().optional(),
|
|
243
|
+
assignee: z.union([z.string(), z.number()]).optional(),
|
|
244
|
+
startTimer: z.boolean().optional(),
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
async ({ name, project, type = "Feature", size = "P", description, checklist = [], listId, parent, assignee, startTimer = true }) => {
|
|
248
|
+
const outcome = await withToolCapture(async () => {
|
|
249
|
+
const created = await task.createTask({
|
|
250
|
+
name,
|
|
251
|
+
project,
|
|
252
|
+
type,
|
|
253
|
+
size,
|
|
254
|
+
description,
|
|
255
|
+
checklist,
|
|
256
|
+
listId,
|
|
257
|
+
parent,
|
|
258
|
+
assignee: assignee ? String(assignee) : undefined,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (!created) {
|
|
262
|
+
throw new Error("Falha ao criar task no ClickUp.");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (startTimer) {
|
|
266
|
+
await task.startTimer(created.id);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return created;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (!outcome.ok) {
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: mergeTextAndLogs(`Erro ao criar task: ${outcome.error}`, outcome.logs) }],
|
|
275
|
+
isError: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return createResponse(
|
|
280
|
+
mergeTextAndLogs(`Task criada com sucesso: ${outcome.result.url}`, outcome.logs),
|
|
281
|
+
{
|
|
282
|
+
task: outcome.result,
|
|
283
|
+
startedTimer: startTimer,
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
server.registerTool(
|
|
290
|
+
"devs_loop_complete_task",
|
|
291
|
+
{
|
|
292
|
+
title: "Concluir task no ClickUp via devs-loop",
|
|
293
|
+
description: "Marca checklist, para timer, conclui a task e registra comentário final.",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
taskId: z.string(),
|
|
296
|
+
comment: z.string().optional(),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
async ({ taskId, comment }) => {
|
|
300
|
+
const outcome = await withToolCapture(async () => {
|
|
301
|
+
await task.completeTask(taskId, comment);
|
|
302
|
+
return { taskId };
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (!outcome.ok) {
|
|
306
|
+
return {
|
|
307
|
+
content: [{ type: "text", text: mergeTextAndLogs(`Erro ao concluir task: ${outcome.error}`, outcome.logs) }],
|
|
308
|
+
isError: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return createResponse(
|
|
313
|
+
mergeTextAndLogs(`Task concluida: ${taskId}`, outcome.logs),
|
|
314
|
+
{ taskId }
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
server.registerTool(
|
|
320
|
+
"devs_loop_timer_status",
|
|
321
|
+
{
|
|
322
|
+
title: "Consultar timer ativo",
|
|
323
|
+
description: "Consulta a task atualmente em andamento no devs-loop.",
|
|
324
|
+
inputSchema: {},
|
|
325
|
+
},
|
|
326
|
+
async () => {
|
|
327
|
+
const current = session.timerStatus();
|
|
328
|
+
if (!current) {
|
|
329
|
+
return createResponse("Nenhum timer ativo.", { active: false });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return createResponse(
|
|
333
|
+
`Timer ativo: ${current.minutes}min na task ${current.taskId}`,
|
|
334
|
+
{ active: true, ...current }
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
server.registerTool(
|
|
340
|
+
"devs_loop_stop_timer",
|
|
341
|
+
{
|
|
342
|
+
title: "Parar timer ativo",
|
|
343
|
+
description: "Para o timer ativo no devs-loop.",
|
|
344
|
+
inputSchema: {},
|
|
345
|
+
},
|
|
346
|
+
async () => {
|
|
347
|
+
const outcome = await withToolCapture(async () => task.stopTimer());
|
|
348
|
+
if (!outcome.ok) {
|
|
349
|
+
return {
|
|
350
|
+
content: [{ type: "text", text: mergeTextAndLogs(`Erro ao parar timer: ${outcome.error}`, outcome.logs) }],
|
|
351
|
+
isError: true,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const result = outcome.result;
|
|
356
|
+
const summary = result?.taskId
|
|
357
|
+
? `Timer parado: ${result.minutes}min na task ${result.taskId}`
|
|
358
|
+
: "Nenhum timer ativo.";
|
|
359
|
+
|
|
360
|
+
return createResponse(mergeTextAndLogs(summary, outcome.logs), result || { taskId: null, minutes: 0 });
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
server.registerTool(
|
|
365
|
+
"devs_loop_summary",
|
|
366
|
+
{
|
|
367
|
+
title: "Resumo da sessão ativa",
|
|
368
|
+
description: "Retorna o resumo da sessão local atual do devs-loop.",
|
|
369
|
+
inputSchema: {},
|
|
370
|
+
},
|
|
371
|
+
async () => {
|
|
372
|
+
const current = session.summary();
|
|
373
|
+
if (!current) {
|
|
374
|
+
return createResponse("Nenhuma sessao ativa.", { active: false });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const text = [
|
|
378
|
+
`Projeto: ${current.project}`,
|
|
379
|
+
`Iniciativa: ${current.initiative}`,
|
|
380
|
+
`Tasks criadas: ${current.tasksCreated}`,
|
|
381
|
+
`Tasks concluidas: ${current.tasksCompleted}`,
|
|
382
|
+
`Tasks pendentes: ${current.tasksPending}`,
|
|
383
|
+
`Tempo total: ${current.totalTime}`,
|
|
384
|
+
].join("\n");
|
|
385
|
+
|
|
386
|
+
return createResponse(text, { active: true, summary: current });
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
server.registerTool(
|
|
391
|
+
"devs_loop_end_session",
|
|
392
|
+
{
|
|
393
|
+
title: "Encerrar sessão do devs-loop",
|
|
394
|
+
description: "Fecha a sessão local, gera resumo e persiste no log de progresso.",
|
|
395
|
+
inputSchema: {},
|
|
396
|
+
},
|
|
397
|
+
async () => {
|
|
398
|
+
const current = session.summary();
|
|
399
|
+
if (!current) {
|
|
400
|
+
return createResponse("Nenhuma sessao ativa para encerrar.", { active: false });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const { result, logs } = await runCaptured(async () => {
|
|
404
|
+
await task.stopTimer();
|
|
405
|
+
const finalSummary = session.summary();
|
|
406
|
+
if (!finalSummary) return null;
|
|
407
|
+
|
|
408
|
+
const listData = finalSummary.listId ? await listResolver.getListById(finalSummary.listId) : null;
|
|
409
|
+
if (listData) {
|
|
410
|
+
finalSummary.listPath = listResolver.formatListPath(listData);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
progress.appendSession(finalSummary);
|
|
414
|
+
learnings.recordSession({
|
|
415
|
+
project: finalSummary.project,
|
|
416
|
+
initiative: finalSummary.initiative,
|
|
417
|
+
tasksCreated: finalSummary.tasksCreated,
|
|
418
|
+
tasksCompleted: finalSummary.tasksCompleted,
|
|
419
|
+
totalMinutes: finalSummary.totalMinutes || 0,
|
|
420
|
+
tasks: (finalSummary.tasks || []).map((taskItem) => ({
|
|
421
|
+
name: taskItem.name,
|
|
422
|
+
type: taskItem.type,
|
|
423
|
+
size: taskItem.size,
|
|
424
|
+
completed: taskItem.completed,
|
|
425
|
+
minutes: taskItem.minutes || 0,
|
|
426
|
+
})),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
for (const taskItem of finalSummary.tasks || []) {
|
|
430
|
+
if (taskItem.completed && taskItem.type && (taskItem.minutes || 0) > 0) {
|
|
431
|
+
learnings.recordTaskTime(taskItem.type, taskItem.minutes);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
session.clearSession();
|
|
436
|
+
return finalSummary;
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (!result) {
|
|
440
|
+
return createResponse("Sessao encerrada sem resumo.", { active: false });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const summaryText = [
|
|
444
|
+
`Sessao encerrada: ${result.project} | ${result.initiative}`,
|
|
445
|
+
`Tasks criadas: ${result.tasksCreated}`,
|
|
446
|
+
`Tasks concluidas: ${result.tasksCompleted}`,
|
|
447
|
+
`Tasks pendentes: ${result.tasksPending}`,
|
|
448
|
+
`Tempo total: ${result.totalTime}`,
|
|
449
|
+
].join("\n");
|
|
450
|
+
|
|
451
|
+
return createResponse(mergeTextAndLogs(summaryText, logs), { active: false, summary: result });
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
async function main() {
|
|
456
|
+
const transport = new StdioServerTransport();
|
|
457
|
+
await server.connect(transport);
|
|
458
|
+
process.stderr.write("[devs-loop-mcp] ready\n");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
main().catch((error) => {
|
|
462
|
+
originalConsoleError("[devs-loop-mcp] fatal", error);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@renatocostaguedesdemorais/devs-loop-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Private distributable MCP and CLI for devs-loop ClickUp automation",
|
|
5
|
+
"main": "cli.cjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"devs-loop": "cli.cjs",
|
|
8
|
+
"devs-loop-mcp": "cli.cjs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node cli.cjs server",
|
|
12
|
+
"cli": "node cli.cjs",
|
|
13
|
+
"mcp": "node cli.cjs server",
|
|
14
|
+
"server": "node cli.cjs server"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"cli.cjs",
|
|
18
|
+
"mcp-server.cjs",
|
|
19
|
+
"README.md",
|
|
20
|
+
"devs-loop.md",
|
|
21
|
+
"config.json",
|
|
22
|
+
".env.example",
|
|
23
|
+
"lib"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"clickup",
|
|
27
|
+
"task",
|
|
28
|
+
"automation",
|
|
29
|
+
"devtools",
|
|
30
|
+
"mcp",
|
|
31
|
+
"modelcontextprotocol"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "restricted"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
+
"zod": "^4.3.6"
|
|
43
|
+
}
|
|
44
|
+
}
|