@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/cli.cjs
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DEVS-LOOP CLI
|
|
4
|
+
// Gestão autônoma de tasks no ClickUp
|
|
5
|
+
// Cross-platform: Windows, macOS, Linux, Termux
|
|
6
|
+
//
|
|
7
|
+
// Uso:
|
|
8
|
+
// node cli.js init --project ATRIO --initiative "Integrar Shopee"
|
|
9
|
+
// node cli.js task --name "Implementar login" --type Feature --size M
|
|
10
|
+
// node cli.js timer start <task_id>
|
|
11
|
+
// node cli.js done <task_id> --comment "Implementado"
|
|
12
|
+
// node cli.js summary
|
|
13
|
+
// node cli.js end
|
|
14
|
+
// node cli.js projects
|
|
15
|
+
// node cli.js install
|
|
16
|
+
// ============================================================
|
|
17
|
+
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const { ensureDir, getHomeConfigDir } = require("./lib/paths.cjs");
|
|
21
|
+
|
|
22
|
+
// Parse args simples
|
|
23
|
+
function parseArgs(args) {
|
|
24
|
+
const parsed = { _: [] };
|
|
25
|
+
let i = 0;
|
|
26
|
+
while (i < args.length) {
|
|
27
|
+
if (args[i].startsWith("--")) {
|
|
28
|
+
const key = args[i].slice(2);
|
|
29
|
+
// Coletar valores até o próximo --flag
|
|
30
|
+
const values = [];
|
|
31
|
+
i++;
|
|
32
|
+
while (i < args.length && !args[i].startsWith("--")) {
|
|
33
|
+
values.push(args[i]);
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
parsed[key] = values.length === 1 ? values[0] : values.length === 0 ? true : values;
|
|
37
|
+
} else {
|
|
38
|
+
parsed._.push(args[i]);
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const args = parseArgs(process.argv.slice(2));
|
|
46
|
+
const command = args._[0];
|
|
47
|
+
const subcommand = args._[1];
|
|
48
|
+
|
|
49
|
+
async function main() {
|
|
50
|
+
switch (command) {
|
|
51
|
+
case "server": {
|
|
52
|
+
require("./mcp-server.cjs");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Sessão ───
|
|
57
|
+
case "init": {
|
|
58
|
+
const config = require("./lib/config.cjs");
|
|
59
|
+
const progress = require("./lib/progress.cjs");
|
|
60
|
+
const session = require("./lib/session.cjs");
|
|
61
|
+
const listResolver = require("./lib/listResolver.cjs");
|
|
62
|
+
|
|
63
|
+
const project = args.project;
|
|
64
|
+
if (!project) {
|
|
65
|
+
console.error("❌ Projeto obrigatório: --project ATRIO");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const projectId = config.resolveProject(project);
|
|
70
|
+
if (!projectId) {
|
|
71
|
+
console.error(`❌ Projeto '${project}' não encontrado. Use: devs-loop projects`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let resolvedList;
|
|
76
|
+
try {
|
|
77
|
+
resolvedList = await listResolver.resolveList({
|
|
78
|
+
project,
|
|
79
|
+
explicitListId: args.list,
|
|
80
|
+
allowPrompt: true,
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Erro: ${error.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const listId = resolvedList.listId;
|
|
88
|
+
const s = session.init({
|
|
89
|
+
project,
|
|
90
|
+
projectId,
|
|
91
|
+
initiative: args.initiative,
|
|
92
|
+
listId,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log("✅ Sessão iniciada");
|
|
96
|
+
console.log(`📋 Projeto: ${project}`);
|
|
97
|
+
console.log(`📋 Iniciativa: ${s.initiative}`);
|
|
98
|
+
if (resolvedList.list) {
|
|
99
|
+
console.log(`📋 Lista: ${listResolver.formatListPath(resolvedList.list)} (${listId})`);
|
|
100
|
+
} else {
|
|
101
|
+
console.log(`📋 Lista: ${listId}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const recent = progress.loadRecentSessions(project, s.initiative, 2);
|
|
105
|
+
const recentText = progress.formatRecentSessions(recent);
|
|
106
|
+
if (recentText) {
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log(recentText);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "end": {
|
|
114
|
+
const learn = require("./lib/learnings.cjs");
|
|
115
|
+
const listResolver = require("./lib/listResolver.cjs");
|
|
116
|
+
const progress = require("./lib/progress.cjs");
|
|
117
|
+
const session = require("./lib/session.cjs");
|
|
118
|
+
const task = require("./lib/task.cjs");
|
|
119
|
+
|
|
120
|
+
// Parar timer ativo
|
|
121
|
+
await task.stopTimer();
|
|
122
|
+
|
|
123
|
+
// Mostrar resumo
|
|
124
|
+
const s = session.summary();
|
|
125
|
+
if (s) {
|
|
126
|
+
const recentList = s.listId ? await listResolver.getListById(s.listId) : null;
|
|
127
|
+
if (recentList) {
|
|
128
|
+
s.listPath = listResolver.formatListPath(recentList);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log("");
|
|
132
|
+
console.log(`📊 Resumo da Sessão — ${s.date}`);
|
|
133
|
+
console.log("━".repeat(40));
|
|
134
|
+
console.log(`Projeto: ${s.project}`);
|
|
135
|
+
console.log(`Iniciativa: ${s.initiative}`);
|
|
136
|
+
console.log(`Tasks criadas: ${s.tasksCreated}`);
|
|
137
|
+
console.log(`Tasks concluídas: ${s.tasksCompleted}`);
|
|
138
|
+
console.log(`Tasks pendentes: ${s.tasksPending}`);
|
|
139
|
+
console.log(`Tempo total: ${s.totalTime}`);
|
|
140
|
+
console.log("━".repeat(40));
|
|
141
|
+
|
|
142
|
+
if (s.tasks.length > 0) {
|
|
143
|
+
console.log("");
|
|
144
|
+
for (const t of s.tasks) {
|
|
145
|
+
const icon = t.completed ? "✅" : "⏳";
|
|
146
|
+
console.log(` ${icon} ${t.name}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.log("");
|
|
150
|
+
|
|
151
|
+
progress.appendSession(s);
|
|
152
|
+
learn.recordSession({
|
|
153
|
+
project: s.project,
|
|
154
|
+
initiative: s.initiative,
|
|
155
|
+
tasksCreated: s.tasksCreated,
|
|
156
|
+
tasksCompleted: s.tasksCompleted,
|
|
157
|
+
totalMinutes: s.totalMinutes || 0,
|
|
158
|
+
tasks: (s.tasks || []).map((taskItem) => ({
|
|
159
|
+
name: taskItem.name,
|
|
160
|
+
type: taskItem.type,
|
|
161
|
+
size: taskItem.size,
|
|
162
|
+
completed: taskItem.completed,
|
|
163
|
+
minutes: taskItem.minutes || 0,
|
|
164
|
+
})),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
for (const taskItem of s.tasks || []) {
|
|
168
|
+
if (taskItem.completed && taskItem.type && (taskItem.minutes || 0) > 0) {
|
|
169
|
+
learn.recordTaskTime(taskItem.type, taskItem.minutes);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
session.clearSession();
|
|
175
|
+
console.log("✅ Sessão encerrada");
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case "summary": {
|
|
180
|
+
const session = require("./lib/session.cjs");
|
|
181
|
+
const s = session.summary();
|
|
182
|
+
if (!s) {
|
|
183
|
+
console.log("⚠️ Nenhuma sessão ativa");
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log("");
|
|
188
|
+
console.log(`📊 Resumo da Sessão — ${s.date}`);
|
|
189
|
+
console.log("━".repeat(40));
|
|
190
|
+
console.log(`Projeto: ${s.project}`);
|
|
191
|
+
console.log(`Iniciativa: ${s.initiative}`);
|
|
192
|
+
console.log(`Tasks criadas: ${s.tasksCreated}`);
|
|
193
|
+
console.log(`Tasks concluídas: ${s.tasksCompleted}`);
|
|
194
|
+
console.log(`Tasks pendentes: ${s.tasksPending}`);
|
|
195
|
+
console.log(`Tempo total: ${s.totalTime}`);
|
|
196
|
+
console.log("━".repeat(40));
|
|
197
|
+
|
|
198
|
+
if (s.tasks.length > 0) {
|
|
199
|
+
console.log("");
|
|
200
|
+
for (const t of s.tasks) {
|
|
201
|
+
const icon = t.completed ? "✅" : "⏳";
|
|
202
|
+
console.log(` ${icon} ${t.name}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const session2 = require("./lib/session.cjs");
|
|
207
|
+
const timer = session2.timerStatus();
|
|
208
|
+
if (timer) {
|
|
209
|
+
console.log("");
|
|
210
|
+
console.log(`⏱️ Timer ativo: ${timer.minutes}min na task ${timer.taskId}`);
|
|
211
|
+
}
|
|
212
|
+
console.log("");
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Tasks ───
|
|
217
|
+
case "task": {
|
|
218
|
+
const task = require("./lib/task.cjs");
|
|
219
|
+
|
|
220
|
+
const checklist = args.checklist
|
|
221
|
+
? Array.isArray(args.checklist) ? args.checklist : [args.checklist]
|
|
222
|
+
: [];
|
|
223
|
+
|
|
224
|
+
const result = await task.createTask({
|
|
225
|
+
name: args.name,
|
|
226
|
+
type: args.type || "Feature",
|
|
227
|
+
size: args.size || "P",
|
|
228
|
+
project: args.project,
|
|
229
|
+
description: args.desc || args.description,
|
|
230
|
+
checklist,
|
|
231
|
+
listId: args.list,
|
|
232
|
+
parent: args.parent,
|
|
233
|
+
assignee: args.assignee,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const shouldStartTimer = result && !args["no-timer"];
|
|
237
|
+
if (shouldStartTimer) {
|
|
238
|
+
await task.startTimer(result.id);
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case "done": {
|
|
244
|
+
const task = require("./lib/task.cjs");
|
|
245
|
+
const taskId = subcommand || args.task;
|
|
246
|
+
|
|
247
|
+
if (!taskId) {
|
|
248
|
+
console.error("❌ Task ID obrigatório: devs-loop done <task_id>");
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await task.completeTask(taskId, args.comment);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─── Attach ───
|
|
257
|
+
case "attach": {
|
|
258
|
+
const https = require("https");
|
|
259
|
+
const { loadEnv } = require("./lib/api.cjs");
|
|
260
|
+
const taskId = subcommand || args.task;
|
|
261
|
+
const filePath = args.file || args._[2];
|
|
262
|
+
if (!taskId) return console.error("❌ Task ID obrigatório: devs-loop attach <task_id> --file caminho/arquivo.ext");
|
|
263
|
+
if (!filePath) return console.error("❌ Arquivo obrigatório: --file caminho/arquivo.ext");
|
|
264
|
+
if (!fs.existsSync(filePath)) return console.error(`❌ Arquivo não encontrado: ${filePath}`);
|
|
265
|
+
|
|
266
|
+
loadEnv();
|
|
267
|
+
|
|
268
|
+
const fileName = path.basename(filePath);
|
|
269
|
+
const fileContent = fs.readFileSync(filePath);
|
|
270
|
+
const boundary = "----DevsLoop" + Date.now();
|
|
271
|
+
|
|
272
|
+
const bodyParts = [
|
|
273
|
+
`--${boundary}\r\n`,
|
|
274
|
+
`Content-Disposition: form-data; name="attachment"; filename="${fileName}"\r\n`,
|
|
275
|
+
`Content-Type: application/octet-stream\r\n\r\n`,
|
|
276
|
+
];
|
|
277
|
+
const bodyEnd = `\r\n--${boundary}--\r\n`;
|
|
278
|
+
|
|
279
|
+
const payload = Buffer.concat([
|
|
280
|
+
Buffer.from(bodyParts.join("")),
|
|
281
|
+
fileContent,
|
|
282
|
+
Buffer.from(bodyEnd),
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
const TOKEN = process.env.CLICKUP_API_TOKEN;
|
|
286
|
+
if (!TOKEN) {
|
|
287
|
+
console.error("❌ CLICKUP_API_TOKEN não definido.");
|
|
288
|
+
console.error(" Configure o token em devs-loop-cjs/.env, .devs-loop/.env ou .env.");
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const result = await new Promise((resolve, reject) => {
|
|
293
|
+
const req = https.request({
|
|
294
|
+
hostname: "api.clickup.com",
|
|
295
|
+
path: `/api/v2/task/${taskId}/attachment`,
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
Authorization: TOKEN,
|
|
299
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
300
|
+
"Content-Length": payload.length,
|
|
301
|
+
},
|
|
302
|
+
}, (res) => {
|
|
303
|
+
let d = ""; res.on("data", (c) => d += c);
|
|
304
|
+
res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve(d); } });
|
|
305
|
+
});
|
|
306
|
+
req.on("error", reject);
|
|
307
|
+
req.write(payload);
|
|
308
|
+
req.end();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (result?.attachment?.url || result?.id) {
|
|
312
|
+
console.log(`📎 Anexo "${fileName}" adicionado à task ${taskId}`);
|
|
313
|
+
} else {
|
|
314
|
+
console.error("❌ Falha ao anexar:", result?.err || result?.message || "erro");
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
case "projects": {
|
|
320
|
+
const config = require("./lib/config.cjs");
|
|
321
|
+
console.log("📋 Projetos disponíveis:");
|
|
322
|
+
for (const p of config.listProjects()) {
|
|
323
|
+
console.log(` → ${p}`);
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─── Timer ───
|
|
329
|
+
case "timer": {
|
|
330
|
+
const task = require("./lib/task.cjs");
|
|
331
|
+
const session = require("./lib/session.cjs");
|
|
332
|
+
|
|
333
|
+
switch (subcommand) {
|
|
334
|
+
case "start": {
|
|
335
|
+
const taskId = args._[2];
|
|
336
|
+
if (!taskId) {
|
|
337
|
+
console.error("❌ Task ID obrigatório: devs-loop timer start <task_id>");
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
await task.startTimer(taskId);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case "stop": {
|
|
344
|
+
await task.stopTimer();
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "status": {
|
|
348
|
+
const t = session.timerStatus();
|
|
349
|
+
if (t) {
|
|
350
|
+
console.log(`⏱️ Timer ativo: ${t.minutes}min na task ${t.taskId}`);
|
|
351
|
+
} else {
|
|
352
|
+
console.log("📋 Nenhum timer ativo");
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
default:
|
|
357
|
+
console.log("Uso: devs-loop timer {start <id>|stop|status}");
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ─── Install ───
|
|
363
|
+
case "install": {
|
|
364
|
+
const projectRoot = findProjectRoot();
|
|
365
|
+
const mdSource = path.join(__dirname, "devs-loop.md");
|
|
366
|
+
const packageConfigSource = path.join(__dirname, "config.json");
|
|
367
|
+
const packageEnvExampleSource = path.join(__dirname, ".env.example");
|
|
368
|
+
const homeConfigDir = ensureDir(getHomeConfigDir());
|
|
369
|
+
const homeConfigTarget = path.join(homeConfigDir, "config.json");
|
|
370
|
+
const homeEnvExampleTarget = path.join(homeConfigDir, ".env.example");
|
|
371
|
+
const homeProgressTarget = path.join(homeConfigDir, "devs-loop-progress.md");
|
|
372
|
+
const homeLearningsTarget = path.join(homeConfigDir, "learnings.json");
|
|
373
|
+
const homeReadmeTarget = path.join(homeConfigDir, "README.txt");
|
|
374
|
+
|
|
375
|
+
if (!fs.existsSync(mdSource)) {
|
|
376
|
+
console.error("❌ devs-loop.md não encontrado");
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(`📦 Instalando DEVS-LOOP em: ${projectRoot}`);
|
|
381
|
+
const md = fs.readFileSync(mdSource, "utf8");
|
|
382
|
+
|
|
383
|
+
if (!fs.existsSync(homeConfigTarget) && fs.existsSync(packageConfigSource)) {
|
|
384
|
+
fs.copyFileSync(packageConfigSource, homeConfigTarget);
|
|
385
|
+
console.log(` ✅ ${homeConfigTarget}`);
|
|
386
|
+
} else if (fs.existsSync(homeConfigTarget)) {
|
|
387
|
+
console.log(` ↺ ${homeConfigTarget}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!fs.existsSync(homeEnvExampleTarget) && fs.existsSync(packageEnvExampleSource)) {
|
|
391
|
+
fs.copyFileSync(packageEnvExampleSource, homeEnvExampleTarget);
|
|
392
|
+
console.log(` ✅ ${homeEnvExampleTarget}`);
|
|
393
|
+
} else if (fs.existsSync(homeEnvExampleTarget)) {
|
|
394
|
+
console.log(` ↺ ${homeEnvExampleTarget}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!fs.existsSync(homeProgressTarget)) {
|
|
398
|
+
fs.writeFileSync(
|
|
399
|
+
homeProgressTarget,
|
|
400
|
+
"# DEVS-LOOP Progress\n\nLog append-only das sessoes de desenvolvimento.\n",
|
|
401
|
+
"utf8"
|
|
402
|
+
);
|
|
403
|
+
console.log(` ✅ ${homeProgressTarget}`);
|
|
404
|
+
} else {
|
|
405
|
+
console.log(` ↺ ${homeProgressTarget}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!fs.existsSync(homeLearningsTarget)) {
|
|
409
|
+
fs.writeFileSync(
|
|
410
|
+
homeLearningsTarget,
|
|
411
|
+
JSON.stringify({
|
|
412
|
+
version: 1,
|
|
413
|
+
lastUpdated: null,
|
|
414
|
+
sessions: { total: 0, history: [] },
|
|
415
|
+
patterns: {},
|
|
416
|
+
projectStats: {},
|
|
417
|
+
taskTypeStats: {},
|
|
418
|
+
avgTimeByType: {},
|
|
419
|
+
customRules: [],
|
|
420
|
+
knownIssues: [],
|
|
421
|
+
devPreferences: {},
|
|
422
|
+
}, null, 2),
|
|
423
|
+
"utf8"
|
|
424
|
+
);
|
|
425
|
+
console.log(` ✅ ${homeLearningsTarget}`);
|
|
426
|
+
} else {
|
|
427
|
+
console.log(` ↺ ${homeLearningsTarget}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!fs.existsSync(homeReadmeTarget)) {
|
|
431
|
+
fs.writeFileSync(
|
|
432
|
+
homeReadmeTarget,
|
|
433
|
+
[
|
|
434
|
+
"Pasta pessoal do devs-loop-mcp.",
|
|
435
|
+
"Arquivos esperados:",
|
|
436
|
+
"- config.json",
|
|
437
|
+
"- .env.example",
|
|
438
|
+
"- .env (criar manualmente com CLICKUP_API_TOKEN e DEV_EMAIL)",
|
|
439
|
+
"- learnings.json",
|
|
440
|
+
"- devs-loop-progress.md",
|
|
441
|
+
].join("\n"),
|
|
442
|
+
"utf8"
|
|
443
|
+
);
|
|
444
|
+
console.log(` ✅ ${homeReadmeTarget}`);
|
|
445
|
+
} else {
|
|
446
|
+
console.log(` ↺ ${homeReadmeTarget}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Claude Code
|
|
450
|
+
fs.writeFileSync(path.join(projectRoot, "CLAUDE.md"), md);
|
|
451
|
+
console.log(" ✅ CLAUDE.md");
|
|
452
|
+
|
|
453
|
+
// Codex
|
|
454
|
+
fs.writeFileSync(path.join(projectRoot, "AGENTS.md"), md);
|
|
455
|
+
console.log(" ✅ AGENTS.md");
|
|
456
|
+
|
|
457
|
+
// Cursor
|
|
458
|
+
fs.writeFileSync(path.join(projectRoot, ".cursorrules"), md);
|
|
459
|
+
console.log(" ✅ .cursorrules");
|
|
460
|
+
|
|
461
|
+
// Windsurf
|
|
462
|
+
fs.writeFileSync(path.join(projectRoot, ".windsurfrules"), md);
|
|
463
|
+
console.log(" ✅ .windsurfrules");
|
|
464
|
+
|
|
465
|
+
// Antigravity
|
|
466
|
+
const skillDir = path.join(projectRoot, ".agents", "skills", "devs-loop");
|
|
467
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
468
|
+
const skillContent = [
|
|
469
|
+
"---",
|
|
470
|
+
"name: devs-loop",
|
|
471
|
+
"description: Gestão autônoma de tasks no ClickUp durante sessões de desenvolvimento.",
|
|
472
|
+
"---",
|
|
473
|
+
"",
|
|
474
|
+
md,
|
|
475
|
+
].join("\n");
|
|
476
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), skillContent);
|
|
477
|
+
console.log(" ✅ .agents/skills/devs-loop/SKILL.md (Antigravity)");
|
|
478
|
+
|
|
479
|
+
console.log("");
|
|
480
|
+
console.log("📁 Config pessoal do pacote:");
|
|
481
|
+
console.log(` → ${homeConfigDir}`);
|
|
482
|
+
console.log("");
|
|
483
|
+
console.log("✅ DEVS-LOOP instalado para todas as IDEs!");
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ─── Coach (orientação proativa) ───
|
|
488
|
+
case "coach": {
|
|
489
|
+
const coach = require("./lib/coach.cjs");
|
|
490
|
+
if (subcommand === "check") {
|
|
491
|
+
const context = {
|
|
492
|
+
currentFile: args.file,
|
|
493
|
+
filesChanged: args.files ? (Array.isArray(args.files) ? args.files : [args.files]) : [],
|
|
494
|
+
hasNewFeature: args.feature === true,
|
|
495
|
+
hasTests: args.tests === true,
|
|
496
|
+
isNewDomain: args.new === true,
|
|
497
|
+
};
|
|
498
|
+
const msg = coach.getProactiveMessage(context);
|
|
499
|
+
console.log(msg || "✅ Tudo em ordem. Segue firme!");
|
|
500
|
+
} else {
|
|
501
|
+
const nudges = coach.analyze({});
|
|
502
|
+
console.log(nudges.length === 0 ? "✅ Sem alertas." : coach.formatNudges(nudges));
|
|
503
|
+
}
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ─── Learn (auto-aprendizado) ───
|
|
508
|
+
case "learn": {
|
|
509
|
+
const learn = require("./lib/learnings.cjs");
|
|
510
|
+
switch (subcommand) {
|
|
511
|
+
case "rule": {
|
|
512
|
+
const text = args._.slice(2).join(" ") || args.rule;
|
|
513
|
+
if (!text) { console.log('Uso: devs-loop learn rule "texto"'); break; }
|
|
514
|
+
learn.addCustomRule(text);
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case "preference": {
|
|
518
|
+
const k = args._[2], v = args._[3];
|
|
519
|
+
if (!k || !v) { console.log("Uso: devs-loop learn preference chave valor"); break; }
|
|
520
|
+
learn.setPreference(k, v);
|
|
521
|
+
console.log(`✅ Preferência: ${k} = ${v}`);
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
case "issue": {
|
|
525
|
+
const issue = args.issue || args._[2];
|
|
526
|
+
if (!issue) { console.log("Uso: devs-loop learn issue --issue 'desc'"); break; }
|
|
527
|
+
learn.addKnownIssue(issue, args.resolution || "");
|
|
528
|
+
console.log("✅ Issue registrada");
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
case "stats": {
|
|
532
|
+
const l = learn.load();
|
|
533
|
+
console.log(`\n📊 Knowledge Base`);
|
|
534
|
+
console.log("━".repeat(35));
|
|
535
|
+
console.log(`Sessões: ${l.sessions.total}`);
|
|
536
|
+
console.log(`Regras: ${l.customRules?.length || 0}`);
|
|
537
|
+
console.log(`Issues: ${l.knownIssues?.length || 0}`);
|
|
538
|
+
if (Object.keys(l.avgTimeByType).length > 0) {
|
|
539
|
+
console.log("\n⏱️ Tempo médio por tipo:");
|
|
540
|
+
for (const [t, d] of Object.entries(l.avgTimeByType)) console.log(` ${t}: ${d.avg}min`);
|
|
541
|
+
}
|
|
542
|
+
if (Object.keys(l.projectStats).length > 0) {
|
|
543
|
+
console.log("\n📁 Projetos:");
|
|
544
|
+
for (const [p, s] of Object.entries(l.projectStats).sort((a, b) => b[1].sessions - a[1].sessions).slice(0, 10))
|
|
545
|
+
console.log(` ${p}: ${s.sessions} sessões`);
|
|
546
|
+
}
|
|
547
|
+
if (l.customRules?.filter(r => r.active).length > 0) {
|
|
548
|
+
console.log("\n📌 Regras:");
|
|
549
|
+
for (const r of l.customRules.filter(r => r.active)) console.log(` → ${r.rule}`);
|
|
550
|
+
}
|
|
551
|
+
console.log("");
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "context": {
|
|
555
|
+
console.log(JSON.stringify(learn.getContextForAgent(args.project), null, 2));
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
default:
|
|
559
|
+
console.log("Uso: devs-loop learn {rule|preference|issue|stats|context}");
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ─── Sync (sincronizar com ClickUp) ───
|
|
565
|
+
case "sync": {
|
|
566
|
+
const { api } = require("./lib/api.cjs");
|
|
567
|
+
const cfg = require("./lib/config.cjs").load();
|
|
568
|
+
console.log("🔄 Sincronizando...");
|
|
569
|
+
try {
|
|
570
|
+
const res = await api.get(`/list/${cfg.default_list}/field`);
|
|
571
|
+
if (res.data?.fields) {
|
|
572
|
+
console.log(` 📋 ${res.data.fields.length} custom fields`);
|
|
573
|
+
for (const f of res.data.fields) {
|
|
574
|
+
const known = Object.values(cfg.custom_fields);
|
|
575
|
+
if (!known.includes(f.id)) console.log(` ⚠️ Não mapeado: "${f.name}" (${f.id})`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
console.log("✅ Sync concluído");
|
|
579
|
+
} catch (e) { console.error("❌", e.message); }
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ─── Help ───
|
|
584
|
+
default: {
|
|
585
|
+
console.log("");
|
|
586
|
+
console.log(" ╔══════════════════════════════════════════════╗");
|
|
587
|
+
console.log(" ║ DEVS-LOOP CLI v2.0 ║");
|
|
588
|
+
console.log(" ║ Gestão autônoma de tasks no ClickUp ║");
|
|
589
|
+
console.log(" ║ Cross-platform: Win / Mac / Linux / Termux ║");
|
|
590
|
+
console.log(" ╚══════════════════════════════════════════════╝");
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log(" Sessão:");
|
|
593
|
+
console.log(" init Iniciar sessão de desenvolvimento");
|
|
594
|
+
console.log(" end Encerrar sessão e gerar resumo");
|
|
595
|
+
console.log(" summary Ver resumo da sessão atual");
|
|
596
|
+
console.log("");
|
|
597
|
+
console.log(" Tasks:");
|
|
598
|
+
console.log(" task Criar task no ClickUp");
|
|
599
|
+
console.log(" done Marcar task como concluída");
|
|
600
|
+
console.log(" attach Anexar arquivo à task");
|
|
601
|
+
console.log(" projects Listar projetos disponíveis");
|
|
602
|
+
console.log("");
|
|
603
|
+
console.log(" Timer:");
|
|
604
|
+
console.log(" timer start <id> Iniciar timer");
|
|
605
|
+
console.log(" timer stop Parar timer");
|
|
606
|
+
console.log(" timer status Ver timer ativo");
|
|
607
|
+
console.log("");
|
|
608
|
+
console.log(" Coach:");
|
|
609
|
+
console.log(" coach Ver alertas e sugestões");
|
|
610
|
+
console.log(" coach check Analisar contexto atual");
|
|
611
|
+
console.log("");
|
|
612
|
+
console.log(" Aprendizado:");
|
|
613
|
+
console.log(' learn rule "..." Adicionar regra customizada');
|
|
614
|
+
console.log(" learn preference Salvar preferência do dev");
|
|
615
|
+
console.log(" learn issue Registrar issue conhecida");
|
|
616
|
+
console.log(" learn stats Ver knowledge base");
|
|
617
|
+
console.log(" learn context Exportar contexto (JSON)");
|
|
618
|
+
console.log("");
|
|
619
|
+
console.log(" Setup:");
|
|
620
|
+
console.log(" server Iniciar o servidor MCP local");
|
|
621
|
+
console.log(" install Distribuir regras para IDEs");
|
|
622
|
+
console.log(" sync Sincronizar com ClickUp");
|
|
623
|
+
console.log("");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function findProjectRoot() {
|
|
629
|
+
let dir = process.cwd();
|
|
630
|
+
while (dir !== path.dirname(dir)) {
|
|
631
|
+
if (fs.existsSync(path.join(dir, ".git"))) return dir;
|
|
632
|
+
dir = path.dirname(dir);
|
|
633
|
+
}
|
|
634
|
+
return process.cwd();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
main().catch((err) => {
|
|
638
|
+
console.error("❌ Erro:", err.message);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
});
|