senq-mcp 1.0.0 → 1.2.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.js +110 -1
- package/dist/setup.js +29 -2
- package/package.json +1 -1
- package/src/index.ts +112 -1
- package/src/setup.ts +32 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ if (process.argv[2] === "setup") {
|
|
|
9
9
|
await runSetup();
|
|
10
10
|
process.exit(0);
|
|
11
11
|
}
|
|
12
|
-
const server = new Server({ name: "senq-mcp", version: "1.
|
|
12
|
+
const server = new Server({ name: "senq-mcp", version: "1.2.0" }, { capabilities: { tools: {} } });
|
|
13
13
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
14
14
|
tools: [
|
|
15
15
|
{
|
|
@@ -106,6 +106,39 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: "list_team_tasks",
|
|
111
|
+
description: "Задачи сотрудника — только для администраторов. Возвращает все задачи, где сотрудник является исполнителем. Можно фильтровать по проекту, области, статусу и диапазону дат.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
required: ["assigneeId"],
|
|
115
|
+
properties: {
|
|
116
|
+
assigneeId: { type: "string", description: "ID сотрудника-исполнителя" },
|
|
117
|
+
projectId: { type: "string", description: "ID проекта (опционально)" },
|
|
118
|
+
areaId: { type: "string", description: "ID области (опционально)" },
|
|
119
|
+
status: { type: "string", enum: ["open", "completed", "all"], description: "Статус задач (по умолчанию all)" },
|
|
120
|
+
dateFrom: { type: "string", description: "Начало периода YYYY-MM-DD" },
|
|
121
|
+
dateTo: { type: "string", description: "Конец периода YYYY-MM-DD" },
|
|
122
|
+
limit: { type: "number", description: "Макс. количество (по умолчанию 200)" },
|
|
123
|
+
offset: { type: "number", description: "Смещение для пагинации" },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "get_team_report",
|
|
129
|
+
description: "Отчёт по задачам сотрудника для администраторов — просрочены, выполнены вовремя, в работе. Принимает фильтры по проекту и диапазону дат (по дате дедлайна).",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
required: ["assigneeId"],
|
|
133
|
+
properties: {
|
|
134
|
+
assigneeId: { type: "string", description: "ID сотрудника" },
|
|
135
|
+
projectId: { type: "string", description: "ID проекта (опционально)" },
|
|
136
|
+
areaId: { type: "string", description: "ID области (опционально)" },
|
|
137
|
+
dateFrom: { type: "string", description: "Начало периода YYYY-MM-DD (фильтр по дедлайну)" },
|
|
138
|
+
dateTo: { type: "string", description: "Конец периода YYYY-MM-DD (фильтр по дедлайну)" },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
109
142
|
],
|
|
110
143
|
}));
|
|
111
144
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -237,6 +270,82 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
237
270
|
const data = await apiGet(apiPath);
|
|
238
271
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
239
272
|
}
|
|
273
|
+
if (name === "list_team_tasks") {
|
|
274
|
+
const assigneeId = String(args?.assigneeId ?? "");
|
|
275
|
+
if (!assigneeId)
|
|
276
|
+
return { content: [{ type: "text", text: "Ошибка: assigneeId обязателен" }] };
|
|
277
|
+
const params = { assigneeId };
|
|
278
|
+
if (args?.projectId)
|
|
279
|
+
params.projectId = String(args.projectId);
|
|
280
|
+
if (args?.areaId)
|
|
281
|
+
params.areaId = String(args.areaId);
|
|
282
|
+
if (args?.status)
|
|
283
|
+
params.status = String(args.status);
|
|
284
|
+
if (args?.dateFrom)
|
|
285
|
+
params.dateFrom = String(args.dateFrom);
|
|
286
|
+
if (args?.dateTo)
|
|
287
|
+
params.dateTo = String(args.dateTo);
|
|
288
|
+
if (args?.limit)
|
|
289
|
+
params.limit = String(args.limit);
|
|
290
|
+
if (args?.offset)
|
|
291
|
+
params.offset = String(args.offset);
|
|
292
|
+
const data = await apiGet("/api/admin/tasks", params);
|
|
293
|
+
const tasks = data.tasks ?? [];
|
|
294
|
+
const total = data.total ?? tasks.length;
|
|
295
|
+
return {
|
|
296
|
+
content: [{
|
|
297
|
+
type: "text",
|
|
298
|
+
text: `Найдено задач: ${total}\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
299
|
+
}],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (name === "get_team_report") {
|
|
303
|
+
const assigneeId = String(args?.assigneeId ?? "");
|
|
304
|
+
if (!assigneeId)
|
|
305
|
+
return { content: [{ type: "text", text: "Ошибка: assigneeId обязателен" }] };
|
|
306
|
+
const params = { assigneeId, status: "all", limit: "500" };
|
|
307
|
+
if (args?.projectId)
|
|
308
|
+
params.projectId = String(args.projectId);
|
|
309
|
+
if (args?.areaId)
|
|
310
|
+
params.areaId = String(args.areaId);
|
|
311
|
+
const data = await apiGet("/api/admin/tasks", params);
|
|
312
|
+
const allTasks = data.tasks ?? [];
|
|
313
|
+
const dateFrom = args?.dateFrom ? String(args.dateFrom) : undefined;
|
|
314
|
+
const dateTo = args?.dateTo ? String(args.dateTo) : undefined;
|
|
315
|
+
const filtered = allTasks.filter((t) => {
|
|
316
|
+
if (!t.dueDate)
|
|
317
|
+
return false;
|
|
318
|
+
if (dateFrom && t.dueDate < dateFrom)
|
|
319
|
+
return false;
|
|
320
|
+
if (dateTo && t.dueDate > dateTo)
|
|
321
|
+
return false;
|
|
322
|
+
return true;
|
|
323
|
+
});
|
|
324
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
325
|
+
const completedOnTime = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) <= t.dueDate);
|
|
326
|
+
const completedLate = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) > t.dueDate);
|
|
327
|
+
const overdue = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate < today);
|
|
328
|
+
const inProgress = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate >= today);
|
|
329
|
+
const fmt = (list) => list.map((t) => ` • ${t.title} (срок: ${t.dueDate ?? "—"}${t.completedAt ? `, выполнена: ${t.completedAt.slice(0, 10)}` : ""}${t.projectName ? `, проект: ${t.projectName}` : ""})`).join("\n");
|
|
330
|
+
const period = dateFrom || dateTo ? ` (период: ${dateFrom ?? "..."} — ${dateTo ?? "..."})` : "";
|
|
331
|
+
const lines = [
|
|
332
|
+
`Отчёт по сотруднику${period}`,
|
|
333
|
+
`Всего задач с дедлайном: ${filtered.length}`,
|
|
334
|
+
"",
|
|
335
|
+
`✅ Выполнено вовремя: ${completedOnTime.length}`,
|
|
336
|
+
completedOnTime.length ? fmt(completedOnTime) : "",
|
|
337
|
+
"",
|
|
338
|
+
`⚠️ Выполнено с опозданием: ${completedLate.length}`,
|
|
339
|
+
completedLate.length ? fmt(completedLate) : "",
|
|
340
|
+
"",
|
|
341
|
+
`🔴 Просрочено (не выполнено): ${overdue.length}`,
|
|
342
|
+
overdue.length ? fmt(overdue) : "",
|
|
343
|
+
"",
|
|
344
|
+
`🔵 В работе (срок не наступил): ${inProgress.length}`,
|
|
345
|
+
inProgress.length ? fmt(inProgress) : "",
|
|
346
|
+
].filter((l) => l !== "");
|
|
347
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
348
|
+
}
|
|
240
349
|
return { content: [{ type: "text", text: `Неизвестный инструмент: ${name}` }] };
|
|
241
350
|
}
|
|
242
351
|
catch (err) {
|
package/dist/setup.js
CHANGED
|
@@ -23,6 +23,7 @@ const CLIENTS = [
|
|
|
23
23
|
{ id: "claude", label: "Claude Desktop", configPath: getConfigPath("claude") },
|
|
24
24
|
{ id: "cursor", label: "Cursor", configPath: getConfigPath("cursor") },
|
|
25
25
|
{ id: "claudecode", label: "Claude Code", configPath: getConfigPath("claudecode") },
|
|
26
|
+
{ id: "codex", label: "Codex", configPath: path.join(os.homedir(), ".codex", "config.toml") },
|
|
26
27
|
];
|
|
27
28
|
function readHidden(prompt) {
|
|
28
29
|
return new Promise((resolve) => {
|
|
@@ -82,6 +83,27 @@ function mergeConfig(configPath, apiKey) {
|
|
|
82
83
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
83
84
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
84
85
|
}
|
|
86
|
+
function mergeCodexConfig(configPath, apiKey) {
|
|
87
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
88
|
+
let existing = "";
|
|
89
|
+
if (fs.existsSync(configPath)) {
|
|
90
|
+
existing = fs.readFileSync(configPath, "utf8");
|
|
91
|
+
}
|
|
92
|
+
const senqBlock = `[mcp_servers.senq]\ncommand = "npx"\nargs = ["senq-mcp"]\n\n[mcp_servers.senq.env]\nSENQ_API_KEY = "${apiKey}"`;
|
|
93
|
+
if (existing.includes("[mcp_servers.senq]")) {
|
|
94
|
+
// Replace existing senq block
|
|
95
|
+
const updated = existing.replace(/\[mcp_servers\.senq\][\s\S]*?SENQ_API_KEY\s*=\s*"[^"]*"/, senqBlock);
|
|
96
|
+
fs.writeFileSync(configPath, updated);
|
|
97
|
+
}
|
|
98
|
+
else if (existing.includes("[sandbox_workspace_write]")) {
|
|
99
|
+
// Insert before sandbox section
|
|
100
|
+
const updated = existing.replace("[sandbox_workspace_write]", `${senqBlock}\n\n[sandbox_workspace_write]`);
|
|
101
|
+
fs.writeFileSync(configPath, updated);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
fs.writeFileSync(configPath, existing + (existing.endsWith("\n") || !existing ? "" : "\n") + "\n" + senqBlock + "\n");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
85
107
|
export async function runSetup() {
|
|
86
108
|
console.log("\n🤖 Настройка Senq MCP\n");
|
|
87
109
|
const apiKey = await readHidden("Вставь API-ключ из Senq: ");
|
|
@@ -94,7 +116,7 @@ export async function runSetup() {
|
|
|
94
116
|
const exists = fs.existsSync(c.configPath);
|
|
95
117
|
console.log(` ${i + 1}) ${c.label}${exists ? " (конфиг найден)" : ""}`);
|
|
96
118
|
});
|
|
97
|
-
console.log(` ${CLIENTS.length + 1}) Все
|
|
119
|
+
console.log(` ${CLIENTS.length + 1}) Все четыре\n`);
|
|
98
120
|
const choice = await ask("Введи номер [1]: ", "1");
|
|
99
121
|
const num = parseInt(choice, 10);
|
|
100
122
|
let selected;
|
|
@@ -110,7 +132,12 @@ export async function runSetup() {
|
|
|
110
132
|
console.log("");
|
|
111
133
|
for (const client of selected) {
|
|
112
134
|
try {
|
|
113
|
-
|
|
135
|
+
if (client.id === "codex") {
|
|
136
|
+
mergeCodexConfig(client.configPath, apiKey);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
mergeConfig(client.configPath, apiKey);
|
|
140
|
+
}
|
|
114
141
|
console.log(` ✅ ${client.label}: ${client.configPath}`);
|
|
115
142
|
}
|
|
116
143
|
catch (err) {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ if (process.argv[2] === "setup") {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const server = new Server(
|
|
18
|
-
{ name: "senq-mcp", version: "1.
|
|
18
|
+
{ name: "senq-mcp", version: "1.2.0" },
|
|
19
19
|
{ capabilities: { tools: {} } },
|
|
20
20
|
);
|
|
21
21
|
|
|
@@ -115,6 +115,39 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
115
115
|
},
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: "list_team_tasks",
|
|
120
|
+
description: "Задачи сотрудника — только для администраторов. Возвращает все задачи, где сотрудник является исполнителем. Можно фильтровать по проекту, области, статусу и диапазону дат.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
required: ["assigneeId"],
|
|
124
|
+
properties: {
|
|
125
|
+
assigneeId: { type: "string", description: "ID сотрудника-исполнителя" },
|
|
126
|
+
projectId: { type: "string", description: "ID проекта (опционально)" },
|
|
127
|
+
areaId: { type: "string", description: "ID области (опционально)" },
|
|
128
|
+
status: { type: "string", enum: ["open", "completed", "all"], description: "Статус задач (по умолчанию all)" },
|
|
129
|
+
dateFrom: { type: "string", description: "Начало периода YYYY-MM-DD" },
|
|
130
|
+
dateTo: { type: "string", description: "Конец периода YYYY-MM-DD" },
|
|
131
|
+
limit: { type: "number", description: "Макс. количество (по умолчанию 200)" },
|
|
132
|
+
offset: { type: "number", description: "Смещение для пагинации" },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "get_team_report",
|
|
138
|
+
description: "Отчёт по задачам сотрудника для администраторов — просрочены, выполнены вовремя, в работе. Принимает фильтры по проекту и диапазону дат (по дате дедлайна).",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
required: ["assigneeId"],
|
|
142
|
+
properties: {
|
|
143
|
+
assigneeId: { type: "string", description: "ID сотрудника" },
|
|
144
|
+
projectId: { type: "string", description: "ID проекта (опционально)" },
|
|
145
|
+
areaId: { type: "string", description: "ID области (опционально)" },
|
|
146
|
+
dateFrom: { type: "string", description: "Начало периода YYYY-MM-DD (фильтр по дедлайну)" },
|
|
147
|
+
dateTo: { type: "string", description: "Конец периода YYYY-MM-DD (фильтр по дедлайну)" },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
118
151
|
],
|
|
119
152
|
}));
|
|
120
153
|
|
|
@@ -222,6 +255,84 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
222
255
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
223
256
|
}
|
|
224
257
|
|
|
258
|
+
if (name === "list_team_tasks") {
|
|
259
|
+
const assigneeId = String(args?.assigneeId ?? "");
|
|
260
|
+
if (!assigneeId) return { content: [{ type: "text", text: "Ошибка: assigneeId обязателен" }] };
|
|
261
|
+
const params: Record<string, string> = { assigneeId };
|
|
262
|
+
if (args?.projectId) params.projectId = String(args.projectId);
|
|
263
|
+
if (args?.areaId) params.areaId = String(args.areaId);
|
|
264
|
+
if (args?.status) params.status = String(args.status);
|
|
265
|
+
if (args?.dateFrom) params.dateFrom = String(args.dateFrom);
|
|
266
|
+
if (args?.dateTo) params.dateTo = String(args.dateTo);
|
|
267
|
+
if (args?.limit) params.limit = String(args.limit);
|
|
268
|
+
if (args?.offset) params.offset = String(args.offset);
|
|
269
|
+
const data = await apiGet<{ tasks?: unknown[]; total?: number }>("/api/admin/tasks", params);
|
|
270
|
+
const tasks = data.tasks ?? [];
|
|
271
|
+
const total = data.total ?? tasks.length;
|
|
272
|
+
return {
|
|
273
|
+
content: [{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: `Найдено задач: ${total}\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
276
|
+
}],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (name === "get_team_report") {
|
|
281
|
+
const assigneeId = String(args?.assigneeId ?? "");
|
|
282
|
+
if (!assigneeId) return { content: [{ type: "text", text: "Ошибка: assigneeId обязателен" }] };
|
|
283
|
+
const params: Record<string, string> = { assigneeId, status: "all", limit: "500" };
|
|
284
|
+
if (args?.projectId) params.projectId = String(args.projectId);
|
|
285
|
+
if (args?.areaId) params.areaId = String(args.areaId);
|
|
286
|
+
|
|
287
|
+
const data = await apiGet<{ tasks?: Array<{
|
|
288
|
+
id: string; title: string; status: string;
|
|
289
|
+
dueDate?: string | null; completedAt?: string | null;
|
|
290
|
+
projectName?: string | null; areaName?: string | null;
|
|
291
|
+
assigneeName?: string | null;
|
|
292
|
+
}>; total?: number }>("/api/admin/tasks", params);
|
|
293
|
+
|
|
294
|
+
const allTasks = data.tasks ?? [];
|
|
295
|
+
const dateFrom = args?.dateFrom ? String(args.dateFrom) : undefined;
|
|
296
|
+
const dateTo = args?.dateTo ? String(args.dateTo) : undefined;
|
|
297
|
+
|
|
298
|
+
const filtered = allTasks.filter((t) => {
|
|
299
|
+
if (!t.dueDate) return false;
|
|
300
|
+
if (dateFrom && t.dueDate < dateFrom) return false;
|
|
301
|
+
if (dateTo && t.dueDate > dateTo) return false;
|
|
302
|
+
return true;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
306
|
+
|
|
307
|
+
const completedOnTime = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) <= t.dueDate);
|
|
308
|
+
const completedLate = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) > t.dueDate);
|
|
309
|
+
const overdue = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate < today);
|
|
310
|
+
const inProgress = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate >= today);
|
|
311
|
+
|
|
312
|
+
const fmt = (list: typeof filtered) =>
|
|
313
|
+
list.map((t) => ` • ${t.title} (срок: ${t.dueDate ?? "—"}${t.completedAt ? `, выполнена: ${t.completedAt.slice(0, 10)}` : ""}${t.projectName ? `, проект: ${t.projectName}` : ""})`).join("\n");
|
|
314
|
+
|
|
315
|
+
const period = dateFrom || dateTo ? ` (период: ${dateFrom ?? "..."} — ${dateTo ?? "..."})` : "";
|
|
316
|
+
const lines = [
|
|
317
|
+
`Отчёт по сотруднику${period}`,
|
|
318
|
+
`Всего задач с дедлайном: ${filtered.length}`,
|
|
319
|
+
"",
|
|
320
|
+
`✅ Выполнено вовремя: ${completedOnTime.length}`,
|
|
321
|
+
completedOnTime.length ? fmt(completedOnTime) : "",
|
|
322
|
+
"",
|
|
323
|
+
`⚠️ Выполнено с опозданием: ${completedLate.length}`,
|
|
324
|
+
completedLate.length ? fmt(completedLate) : "",
|
|
325
|
+
"",
|
|
326
|
+
`🔴 Просрочено (не выполнено): ${overdue.length}`,
|
|
327
|
+
overdue.length ? fmt(overdue) : "",
|
|
328
|
+
"",
|
|
329
|
+
`🔵 В работе (срок не наступил): ${inProgress.length}`,
|
|
330
|
+
inProgress.length ? fmt(inProgress) : "",
|
|
331
|
+
].filter((l) => l !== "");
|
|
332
|
+
|
|
333
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
334
|
+
}
|
|
335
|
+
|
|
225
336
|
return { content: [{ type: "text", text: `Неизвестный инструмент: ${name}` }] };
|
|
226
337
|
} catch (err) {
|
|
227
338
|
const msg = err instanceof Error ? err.message : String(err);
|
package/src/setup.ts
CHANGED
|
@@ -31,6 +31,7 @@ const CLIENTS: ClientInfo[] = [
|
|
|
31
31
|
{ id: "claude", label: "Claude Desktop", configPath: getConfigPath("claude") },
|
|
32
32
|
{ id: "cursor", label: "Cursor", configPath: getConfigPath("cursor") },
|
|
33
33
|
{ id: "claudecode", label: "Claude Code", configPath: getConfigPath("claudecode") },
|
|
34
|
+
{ id: "codex", label: "Codex", configPath: path.join(os.homedir(), ".codex", "config.toml") },
|
|
34
35
|
];
|
|
35
36
|
|
|
36
37
|
function readHidden(prompt: string): Promise<string> {
|
|
@@ -87,6 +88,31 @@ function mergeConfig(configPath: string, apiKey: string): void {
|
|
|
87
88
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
function mergeCodexConfig(configPath: string, apiKey: string): void {
|
|
92
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
93
|
+
let existing = "";
|
|
94
|
+
if (fs.existsSync(configPath)) {
|
|
95
|
+
existing = fs.readFileSync(configPath, "utf8");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const senqBlock = `[mcp_servers.senq]\ncommand = "npx"\nargs = ["senq-mcp"]\n\n[mcp_servers.senq.env]\nSENQ_API_KEY = "${apiKey}"`;
|
|
99
|
+
|
|
100
|
+
if (existing.includes("[mcp_servers.senq]")) {
|
|
101
|
+
// Replace existing senq block
|
|
102
|
+
const updated = existing.replace(
|
|
103
|
+
/\[mcp_servers\.senq\][\s\S]*?SENQ_API_KEY\s*=\s*"[^"]*"/,
|
|
104
|
+
senqBlock,
|
|
105
|
+
);
|
|
106
|
+
fs.writeFileSync(configPath, updated);
|
|
107
|
+
} else if (existing.includes("[sandbox_workspace_write]")) {
|
|
108
|
+
// Insert before sandbox section
|
|
109
|
+
const updated = existing.replace("[sandbox_workspace_write]", `${senqBlock}\n\n[sandbox_workspace_write]`);
|
|
110
|
+
fs.writeFileSync(configPath, updated);
|
|
111
|
+
} else {
|
|
112
|
+
fs.writeFileSync(configPath, existing + (existing.endsWith("\n") || !existing ? "" : "\n") + "\n" + senqBlock + "\n");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
export async function runSetup(): Promise<void> {
|
|
91
117
|
console.log("\n🤖 Настройка Senq MCP\n");
|
|
92
118
|
|
|
@@ -102,7 +128,7 @@ export async function runSetup(): Promise<void> {
|
|
|
102
128
|
const exists = fs.existsSync(c.configPath);
|
|
103
129
|
console.log(` ${i + 1}) ${c.label}${exists ? " (конфиг найден)" : ""}`);
|
|
104
130
|
});
|
|
105
|
-
console.log(` ${CLIENTS.length + 1}) Все
|
|
131
|
+
console.log(` ${CLIENTS.length + 1}) Все четыре\n`);
|
|
106
132
|
|
|
107
133
|
const choice = await ask("Введи номер [1]: ", "1");
|
|
108
134
|
const num = parseInt(choice, 10);
|
|
@@ -119,7 +145,11 @@ export async function runSetup(): Promise<void> {
|
|
|
119
145
|
console.log("");
|
|
120
146
|
for (const client of selected) {
|
|
121
147
|
try {
|
|
122
|
-
|
|
148
|
+
if (client.id === "codex") {
|
|
149
|
+
mergeCodexConfig(client.configPath, apiKey);
|
|
150
|
+
} else {
|
|
151
|
+
mergeConfig(client.configPath, apiKey);
|
|
152
|
+
}
|
|
123
153
|
console.log(` ✅ ${client.label}: ${client.configPath}`);
|
|
124
154
|
} catch (err) {
|
|
125
155
|
const msg = err instanceof Error ? err.message : String(err);
|