taskin 1.0.5 → 1.0.7
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/dashboard-dist/assets/index-BUUmUx2c.js +21 -0
- package/dashboard-dist/assets/index-C7xLvQXX.css +1 -0
- package/dashboard-dist/index.html +14 -0
- package/dist/index.js +1058 -174
- package/package.json +32 -18
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
4
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
5
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -9,6 +10,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
10
|
var __esm = (fn, res) => function __init() {
|
|
10
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
12
|
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
12
17
|
|
|
13
18
|
// ../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
14
19
|
import path from "path";
|
|
@@ -23,11 +28,172 @@ var init_esm_shims = __esm({
|
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
30
|
|
|
31
|
+
// ../fs-task-provider/dist/task-validator.js
|
|
32
|
+
var task_validator_exports = {};
|
|
33
|
+
__export(task_validator_exports, {
|
|
34
|
+
createLintResult: () => createLintResult,
|
|
35
|
+
fixTaskFile: () => fixTaskFile,
|
|
36
|
+
validateTaskFile: () => validateTaskFile
|
|
37
|
+
});
|
|
38
|
+
import { readFile, writeFile } from "fs/promises";
|
|
39
|
+
async function fixTaskFile(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
const content = await readFile(filePath, "utf-8");
|
|
42
|
+
const hasInlineStatus = /^Status:\s*.+$/im.test(content);
|
|
43
|
+
const hasInlineType = /^Type:\s*.+$/im.test(content);
|
|
44
|
+
const hasInlineAssignee = /^Assignee:\s*.+$/im.test(content);
|
|
45
|
+
if (!hasInlineStatus && !hasInlineType && !hasInlineAssignee) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const statusMatch = content.match(/^Status:\s*(.+)$/im);
|
|
49
|
+
const typeMatch = content.match(/^Type:\s*(.+)$/im);
|
|
50
|
+
const assigneeMatch = content.match(/^Assignee:\s*(.+)$/im);
|
|
51
|
+
let newContent = content;
|
|
52
|
+
if (statusMatch) {
|
|
53
|
+
newContent = newContent.replace(/^Status:\s*.+$/im, "");
|
|
54
|
+
}
|
|
55
|
+
if (typeMatch) {
|
|
56
|
+
newContent = newContent.replace(/^Type:\s*.+$/im, "");
|
|
57
|
+
}
|
|
58
|
+
if (assigneeMatch) {
|
|
59
|
+
newContent = newContent.replace(/^Assignee:\s*.+$/im, "");
|
|
60
|
+
}
|
|
61
|
+
newContent = newContent.replace(/\n{3,}/g, "\n\n");
|
|
62
|
+
const titleLineIdx = newContent.split("\n").findIndex((line) => line.trim().startsWith("# "));
|
|
63
|
+
if (titleLineIdx === -1) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const contentLines = newContent.split("\n");
|
|
67
|
+
const beforeTitle = contentLines.slice(0, titleLineIdx + 1);
|
|
68
|
+
const afterTitle = contentLines.slice(titleLineIdx + 1);
|
|
69
|
+
const sections = [];
|
|
70
|
+
if (statusMatch) {
|
|
71
|
+
sections.push("", "## Status", "", statusMatch[1].trim());
|
|
72
|
+
}
|
|
73
|
+
if (typeMatch) {
|
|
74
|
+
sections.push("", "## Type", "", typeMatch[1].trim());
|
|
75
|
+
}
|
|
76
|
+
if (assigneeMatch) {
|
|
77
|
+
sections.push("", "## Assignee", "", assigneeMatch[1].trim());
|
|
78
|
+
}
|
|
79
|
+
const fixed = [...beforeTitle, ...sections, "", ...afterTitle].join("\n");
|
|
80
|
+
const finalContent = fixed.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
81
|
+
await writeFile(filePath, finalContent, "utf-8");
|
|
82
|
+
return true;
|
|
83
|
+
} catch (error2) {
|
|
84
|
+
console.error(`Failed to fix ${filePath}:`, error2);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function validateTaskFile(filePath) {
|
|
89
|
+
const issues = [];
|
|
90
|
+
try {
|
|
91
|
+
const content = await readFile(filePath, "utf-8");
|
|
92
|
+
const lines = content.split("\n");
|
|
93
|
+
const hasTitleSection = lines.some((line) => line.trim().startsWith("# "));
|
|
94
|
+
const hasStatusSection = content.includes("## Status");
|
|
95
|
+
const hasInlineStatus = /^Status:\s*.+$/im.test(content);
|
|
96
|
+
const hasDescriptionSection = content.includes("## Description") || content.includes("## Descri\xE7\xE3o");
|
|
97
|
+
if (!hasTitleSection) {
|
|
98
|
+
issues.push({
|
|
99
|
+
file: filePath,
|
|
100
|
+
line: 1,
|
|
101
|
+
message: "Task file must start with a title (# Task Title)",
|
|
102
|
+
severity: "error",
|
|
103
|
+
suggestion: "Add a level-1 heading at the start: # Your Task Title"
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (hasInlineStatus) {
|
|
107
|
+
const statusLineIdx = lines.findIndex((line) => /^Status:/i.test(line.trim()));
|
|
108
|
+
issues.push({
|
|
109
|
+
file: filePath,
|
|
110
|
+
line: statusLineIdx >= 0 ? statusLineIdx + 1 : void 0,
|
|
111
|
+
message: 'Inline metadata ("Status: ...") is not allowed. Use a "## Status" section instead.',
|
|
112
|
+
severity: "error",
|
|
113
|
+
suggestion: "Replace inline metadata with a section:\n## Status\n<todo|in-progress|done>"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (!hasStatusSection) {
|
|
117
|
+
issues.push({
|
|
118
|
+
file: filePath,
|
|
119
|
+
message: "Task file must have a ## Status section",
|
|
120
|
+
severity: "error",
|
|
121
|
+
suggestion: "Add a section:\n## Status\n<todo|in-progress|done>"
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
const statusMatch = content.match(/## Status\s*\n\s*([^\n\r]+)/i);
|
|
125
|
+
const statusValue = statusMatch ? statusMatch[1].trim().toLowerCase() : "";
|
|
126
|
+
if (!["todo", "in-progress", "done", "pending"].includes(statusValue)) {
|
|
127
|
+
const statusLineIdx = lines.findIndex((line) => line.trim() === "## Status");
|
|
128
|
+
issues.push({
|
|
129
|
+
file: filePath,
|
|
130
|
+
line: statusLineIdx >= 0 ? statusLineIdx + 2 : void 0,
|
|
131
|
+
message: "Status must be one of: todo, in-progress, done, pending",
|
|
132
|
+
severity: "error",
|
|
133
|
+
suggestion: "Set status to: todo, in-progress, done, or pending"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!hasDescriptionSection) {
|
|
138
|
+
issues.push({
|
|
139
|
+
file: filePath,
|
|
140
|
+
message: "Task file should have a description section (## Description or ## Descri\xE7\xE3o)",
|
|
141
|
+
severity: "warning",
|
|
142
|
+
suggestion: "Add a description section to explain the task"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const fileName = filePath.split("/").pop() || "";
|
|
146
|
+
if (!fileName.match(/^task-\d{3}-.*\.md$/)) {
|
|
147
|
+
issues.push({
|
|
148
|
+
file: filePath,
|
|
149
|
+
message: "Task filename should follow pattern: task-NNN-description.md (e.g., task-001-my-task.md)",
|
|
150
|
+
severity: "warning",
|
|
151
|
+
suggestion: "Rename the file to match the pattern task-001-description.md"
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const titleLine = lines.findIndex((line) => line.trim().startsWith("# "));
|
|
155
|
+
if (titleLine >= 0 && lines[titleLine].trim() === "#") {
|
|
156
|
+
issues.push({
|
|
157
|
+
file: filePath,
|
|
158
|
+
line: titleLine + 1,
|
|
159
|
+
message: "Task title cannot be empty",
|
|
160
|
+
severity: "error",
|
|
161
|
+
suggestion: "Add a meaningful title after the # symbol"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
} catch (error2) {
|
|
165
|
+
issues.push({
|
|
166
|
+
file: filePath,
|
|
167
|
+
message: `Failed to read or parse task file: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
168
|
+
severity: "error"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return issues;
|
|
172
|
+
}
|
|
173
|
+
function createLintResult(allIssues) {
|
|
174
|
+
const errorCount = allIssues.filter((issue) => issue.severity === "error").length;
|
|
175
|
+
const warningCount = allIssues.filter((issue) => issue.severity === "warning").length;
|
|
176
|
+
const infoCount = allIssues.filter((issue) => issue.severity === "info").length;
|
|
177
|
+
return {
|
|
178
|
+
valid: errorCount === 0,
|
|
179
|
+
issues: allIssues,
|
|
180
|
+
errorCount,
|
|
181
|
+
warningCount,
|
|
182
|
+
infoCount
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
var init_task_validator = __esm({
|
|
186
|
+
"../fs-task-provider/dist/task-validator.js"() {
|
|
187
|
+
"use strict";
|
|
188
|
+
init_esm_shims();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
26
192
|
// src/index.ts
|
|
27
193
|
init_esm_shims();
|
|
28
194
|
import { Command } from "commander";
|
|
29
195
|
|
|
30
|
-
// src/commands/
|
|
196
|
+
// src/commands/dashboard.ts
|
|
31
197
|
init_esm_shims();
|
|
32
198
|
|
|
33
199
|
// ../fs-task-provider/dist/index.js
|
|
@@ -37,10 +203,53 @@ init_esm_shims();
|
|
|
37
203
|
init_esm_shims();
|
|
38
204
|
import { promises as fs } from "fs";
|
|
39
205
|
import path2 from "path";
|
|
206
|
+
|
|
207
|
+
// ../fs-task-provider/dist/i18n.js
|
|
208
|
+
init_esm_shims();
|
|
209
|
+
var i18nConfig = {
|
|
210
|
+
"en-US": {
|
|
211
|
+
status: "Status",
|
|
212
|
+
type: "Type",
|
|
213
|
+
assignee: "Assignee",
|
|
214
|
+
description: "Description",
|
|
215
|
+
tasks: "Tasks",
|
|
216
|
+
notes: "Notes",
|
|
217
|
+
defaultAssignee: "To be defined",
|
|
218
|
+
descriptionPlaceholder: "Add task description here...",
|
|
219
|
+
notesPlaceholder: "Add any relevant notes or links here."
|
|
220
|
+
},
|
|
221
|
+
"pt-BR": {
|
|
222
|
+
status: "Status",
|
|
223
|
+
type: "Tipo",
|
|
224
|
+
assignee: "Respons\xE1vel",
|
|
225
|
+
description: "Descri\xE7\xE3o",
|
|
226
|
+
tasks: "Tarefas",
|
|
227
|
+
notes: "Notas",
|
|
228
|
+
defaultAssignee: "A definir",
|
|
229
|
+
descriptionPlaceholder: "Adicione a descri\xE7\xE3o da tarefa aqui...",
|
|
230
|
+
notesPlaceholder: "Adicione notas ou links relevantes aqui."
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function getI18n(locale = "en-US") {
|
|
234
|
+
return i18nConfig[locale];
|
|
235
|
+
}
|
|
236
|
+
function detectLocale(content) {
|
|
237
|
+
if (content.includes("## Descri\xE7\xE3o") || content.includes("## Tipo") || content.includes("## Respons\xE1vel") || content.includes("## Tarefas")) {
|
|
238
|
+
return "pt-BR";
|
|
239
|
+
}
|
|
240
|
+
return "en-US";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ../fs-task-provider/dist/fs-task-provider.js
|
|
244
|
+
init_task_validator();
|
|
40
245
|
var FileSystemTaskProvider = class {
|
|
41
246
|
tasksDirectory;
|
|
42
|
-
|
|
247
|
+
userRegistry;
|
|
248
|
+
locale;
|
|
249
|
+
constructor(tasksDirectory, userRegistry, locale = "en-US") {
|
|
43
250
|
this.tasksDirectory = tasksDirectory;
|
|
251
|
+
this.userRegistry = userRegistry;
|
|
252
|
+
this.locale = locale;
|
|
44
253
|
}
|
|
45
254
|
async findTask(taskId) {
|
|
46
255
|
const files = await fs.readdir(this.tasksDirectory);
|
|
@@ -52,25 +261,59 @@ var FileSystemTaskProvider = class {
|
|
|
52
261
|
const content = await fs.readFile(filePath, "utf-8");
|
|
53
262
|
const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
|
|
54
263
|
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
264
|
+
const contentLocale = detectLocale(content);
|
|
265
|
+
const i18n = getI18n(contentLocale);
|
|
266
|
+
const extractSection = (name, localizedName) => {
|
|
267
|
+
const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
|
|
268
|
+
for (const n of names) {
|
|
269
|
+
const rx = new RegExp(`##\\s*${n}\\s*\\n\\s*([^\\n\\r]+)`, "i");
|
|
270
|
+
const m = content.match(rx);
|
|
271
|
+
if (m)
|
|
272
|
+
return m[1].trim();
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
};
|
|
276
|
+
const statusMatch = extractSection("Status", i18n.status);
|
|
277
|
+
const typeMatch = extractSection("Type", i18n.type);
|
|
278
|
+
const assigneeMatch = extractSection("Assignee", i18n.assignee);
|
|
279
|
+
let assignee;
|
|
280
|
+
if (assigneeMatch) {
|
|
281
|
+
const assigneeValue = assigneeMatch.trim();
|
|
282
|
+
assignee = this.userRegistry.resolveUser(assigneeValue);
|
|
283
|
+
if (!assignee) {
|
|
284
|
+
console.warn(`[FS Provider] User "${assigneeValue}" not found in registry, creating temporary user`);
|
|
285
|
+
assignee = this.userRegistry.createTemporaryUser(assigneeValue);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
59
288
|
const task = {
|
|
60
289
|
id: taskId,
|
|
61
290
|
title,
|
|
62
291
|
content,
|
|
63
292
|
filePath,
|
|
64
|
-
|
|
65
|
-
status: statusMatch ? statusMatch
|
|
66
|
-
type: typeMatch ? typeMatch
|
|
293
|
+
assignee,
|
|
294
|
+
status: statusMatch ? statusMatch.trim().toLowerCase() : "pending",
|
|
295
|
+
type: typeMatch ? typeMatch.trim().toLowerCase() : "feat",
|
|
67
296
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
68
297
|
};
|
|
69
298
|
return task;
|
|
70
299
|
}
|
|
71
300
|
async updateTask(task) {
|
|
72
301
|
const currentContent = await fs.readFile(task.filePath, "utf-8");
|
|
73
|
-
const
|
|
302
|
+
const hasInlineMetadata = /^(Status|Type|Assignee):\s*.+$/im.test(currentContent);
|
|
303
|
+
if (hasInlineMetadata) {
|
|
304
|
+
const { fixTaskFile: fixTaskFile2 } = await Promise.resolve().then(() => (init_task_validator(), task_validator_exports));
|
|
305
|
+
await fixTaskFile2(task.filePath);
|
|
306
|
+
}
|
|
307
|
+
const content = await fs.readFile(task.filePath, "utf-8");
|
|
308
|
+
let updatedContent;
|
|
309
|
+
if (/##\s*Status/i.test(content)) {
|
|
310
|
+
updatedContent = content.replace(/(##\s*Status\s*\n\s*)([^\n\r]*)/i, `$1${task.status}`);
|
|
311
|
+
} else {
|
|
312
|
+
updatedContent = content.replace(/(^#.*\n)/, `$1
|
|
313
|
+
## Status
|
|
314
|
+
${task.status}
|
|
315
|
+
`);
|
|
316
|
+
}
|
|
74
317
|
await fs.writeFile(task.filePath, updatedContent, "utf-8");
|
|
75
318
|
}
|
|
76
319
|
async getAllTasks() {
|
|
@@ -84,24 +327,236 @@ var FileSystemTaskProvider = class {
|
|
|
84
327
|
const taskId = idMatch ? idMatch[1] : "unknown";
|
|
85
328
|
const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
|
|
86
329
|
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
330
|
+
const contentLocale = detectLocale(content);
|
|
331
|
+
const i18n = getI18n(contentLocale);
|
|
332
|
+
const extractSection = (name, localizedName) => {
|
|
333
|
+
const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
|
|
334
|
+
for (const n of names) {
|
|
335
|
+
const rx = new RegExp(`##\\s*${n}\\s*\\n\\s*([^\\n\\r]+)`, "i");
|
|
336
|
+
const m = content.match(rx);
|
|
337
|
+
if (m)
|
|
338
|
+
return m[1].trim();
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
};
|
|
342
|
+
const statusMatch = extractSection("Status", i18n.status);
|
|
343
|
+
const typeMatch = extractSection("Type", i18n.type);
|
|
344
|
+
const assigneeMatch = extractSection("Assignee", i18n.assignee);
|
|
345
|
+
let assignee;
|
|
346
|
+
if (assigneeMatch) {
|
|
347
|
+
const assigneeValue = assigneeMatch.trim();
|
|
348
|
+
assignee = this.userRegistry.resolveUser(assigneeValue);
|
|
349
|
+
if (!assignee) {
|
|
350
|
+
assignee = this.userRegistry.createTemporaryUser(assigneeValue);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
91
353
|
const task = {
|
|
92
354
|
id: taskId,
|
|
93
355
|
title,
|
|
94
356
|
content,
|
|
95
357
|
filePath,
|
|
96
|
-
|
|
97
|
-
status: statusMatch ? statusMatch
|
|
98
|
-
type: typeMatch ? typeMatch
|
|
358
|
+
assignee,
|
|
359
|
+
status: statusMatch ? statusMatch.trim().toLowerCase() : "pending",
|
|
360
|
+
type: typeMatch ? typeMatch.trim().toLowerCase() : "feat",
|
|
99
361
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
100
362
|
};
|
|
101
363
|
tasks.push(task);
|
|
102
364
|
}
|
|
103
365
|
return tasks;
|
|
104
366
|
}
|
|
367
|
+
async createTask(options) {
|
|
368
|
+
const i18n = getI18n(this.locale);
|
|
369
|
+
const allTasks = await this.getAllTasks();
|
|
370
|
+
const taskNumbers = allTasks.map((task2) => {
|
|
371
|
+
const match = task2.id.match(/^(\d+)$/);
|
|
372
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
373
|
+
}).filter((num) => !isNaN(num));
|
|
374
|
+
const nextNumber = taskNumbers.length > 0 ? Math.max(...taskNumbers) + 1 : 1;
|
|
375
|
+
const taskId = String(nextNumber).padStart(3, "0");
|
|
376
|
+
const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
377
|
+
const fileName = `task-${taskId}-${titleSlug}.md`;
|
|
378
|
+
const filePath = path2.join(this.tasksDirectory, fileName);
|
|
379
|
+
const fileExists = await fs.access(filePath).then(() => true).catch(() => false);
|
|
380
|
+
if (fileExists) {
|
|
381
|
+
throw new Error(`Task file already exists: ${fileName}`);
|
|
382
|
+
}
|
|
383
|
+
let assignee;
|
|
384
|
+
if (options.assignee) {
|
|
385
|
+
assignee = this.userRegistry.resolveUser(options.assignee);
|
|
386
|
+
if (!assignee) {
|
|
387
|
+
assignee = this.userRegistry.createTemporaryUser(options.assignee);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const taskContent = this.generateTaskMarkdown({
|
|
391
|
+
id: taskId,
|
|
392
|
+
title: options.title,
|
|
393
|
+
type: options.type,
|
|
394
|
+
description: options.description || "",
|
|
395
|
+
assignee: assignee?.name || i18n.defaultAssignee,
|
|
396
|
+
i18n
|
|
397
|
+
});
|
|
398
|
+
await fs.writeFile(filePath, taskContent, "utf-8");
|
|
399
|
+
const task = await this.findTask(taskId);
|
|
400
|
+
if (!task) {
|
|
401
|
+
throw new Error(`Failed to create task ${taskId}`);
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
task,
|
|
405
|
+
taskId,
|
|
406
|
+
filePath
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
generateTaskMarkdown(data) {
|
|
410
|
+
const { id, title, type, description, assignee, i18n } = data;
|
|
411
|
+
return `# \u{1F9E9} Task ${id} \u2014 ${title}
|
|
412
|
+
|
|
413
|
+
## ${i18n.status}
|
|
414
|
+
|
|
415
|
+
pending
|
|
416
|
+
|
|
417
|
+
## ${i18n.type}
|
|
418
|
+
|
|
419
|
+
${type}
|
|
420
|
+
|
|
421
|
+
## ${i18n.assignee}
|
|
422
|
+
|
|
423
|
+
${assignee}
|
|
424
|
+
|
|
425
|
+
## ${i18n.description}
|
|
426
|
+
|
|
427
|
+
${description || i18n.descriptionPlaceholder}
|
|
428
|
+
|
|
429
|
+
## ${i18n.tasks}
|
|
430
|
+
|
|
431
|
+
- [ ] Task 1
|
|
432
|
+
- [ ] Task 2
|
|
433
|
+
- [ ] Task 3
|
|
434
|
+
|
|
435
|
+
## ${i18n.notes}
|
|
436
|
+
|
|
437
|
+
${i18n.notesPlaceholder}
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
async lint(fix) {
|
|
441
|
+
const files = await fs.readdir(this.tasksDirectory);
|
|
442
|
+
const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md")).map((file) => path2.join(this.tasksDirectory, file));
|
|
443
|
+
if (fix) {
|
|
444
|
+
let fixedCount = 0;
|
|
445
|
+
for (const filePath of taskFiles) {
|
|
446
|
+
const wasFixed = await fixTaskFile(filePath);
|
|
447
|
+
if (wasFixed) {
|
|
448
|
+
fixedCount++;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (fixedCount > 0) {
|
|
452
|
+
console.log(`\u2728 Fixed ${fixedCount} task file(s)`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const allIssues = [];
|
|
456
|
+
for (const filePath of taskFiles) {
|
|
457
|
+
const issues = await validateTaskFile(filePath);
|
|
458
|
+
allIssues.push(...issues);
|
|
459
|
+
}
|
|
460
|
+
return createLintResult(allIssues);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// ../fs-task-provider/dist/user-registry.js
|
|
465
|
+
init_esm_shims();
|
|
466
|
+
import { promises as fs2 } from "fs";
|
|
467
|
+
import path3 from "path";
|
|
468
|
+
var UserRegistry = class {
|
|
469
|
+
config;
|
|
470
|
+
users = /* @__PURE__ */ new Map();
|
|
471
|
+
usersFilePath;
|
|
472
|
+
constructor(config) {
|
|
473
|
+
this.config = config;
|
|
474
|
+
this.usersFilePath = path3.join(config.taskinDir, "users.json");
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Load users from users.json
|
|
478
|
+
*/
|
|
479
|
+
async load() {
|
|
480
|
+
try {
|
|
481
|
+
const content = await fs2.readFile(this.usersFilePath, "utf-8");
|
|
482
|
+
const data = JSON.parse(content);
|
|
483
|
+
this.users.clear();
|
|
484
|
+
for (const [id, user] of Object.entries(data.users)) {
|
|
485
|
+
this.users.set(id, user);
|
|
486
|
+
}
|
|
487
|
+
console.log(`[UserRegistry] Loaded ${this.users.size} users`);
|
|
488
|
+
} catch (error2) {
|
|
489
|
+
if (error2.code === "ENOENT") {
|
|
490
|
+
console.warn(
|
|
491
|
+
"[UserRegistry] users.json not found, starting with empty registry"
|
|
492
|
+
);
|
|
493
|
+
this.users.clear();
|
|
494
|
+
} else {
|
|
495
|
+
throw error2;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get user by ID
|
|
501
|
+
*/
|
|
502
|
+
getUser(userId) {
|
|
503
|
+
return this.users.get(userId);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Resolve user from name or ID
|
|
507
|
+
* Tries to match by ID first, then by name
|
|
508
|
+
*/
|
|
509
|
+
resolveUser(nameOrId) {
|
|
510
|
+
const byId = this.users.get(nameOrId);
|
|
511
|
+
if (byId) return byId;
|
|
512
|
+
const slug = nameOrId.toLowerCase().replace(/\s+/g, "-");
|
|
513
|
+
const bySlug = this.users.get(slug);
|
|
514
|
+
if (bySlug) return bySlug;
|
|
515
|
+
for (const user of this.users.values()) {
|
|
516
|
+
if (user.name.toLowerCase() === nameOrId.toLowerCase()) {
|
|
517
|
+
return user;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Create a temporary user if not found in registry
|
|
524
|
+
* Useful for backward compatibility
|
|
525
|
+
*/
|
|
526
|
+
createTemporaryUser(nameOrId) {
|
|
527
|
+
const slug = nameOrId.toLowerCase().replace(/\s+/g, "-");
|
|
528
|
+
return {
|
|
529
|
+
id: slug,
|
|
530
|
+
name: nameOrId,
|
|
531
|
+
email: `${slug.replace(/\s+/g, ".")}@example.com`
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get all users
|
|
536
|
+
*/
|
|
537
|
+
getAllUsers() {
|
|
538
|
+
return Array.from(this.users.values());
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Add or update a user
|
|
542
|
+
*/
|
|
543
|
+
async saveUser(user) {
|
|
544
|
+
this.users.set(user.id, user);
|
|
545
|
+
await this.save();
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Save users to users.json
|
|
549
|
+
*/
|
|
550
|
+
async save() {
|
|
551
|
+
const data = {
|
|
552
|
+
users: Object.fromEntries(this.users.entries())
|
|
553
|
+
};
|
|
554
|
+
await fs2.writeFile(
|
|
555
|
+
this.usersFilePath,
|
|
556
|
+
JSON.stringify(data, null, 2),
|
|
557
|
+
"utf-8"
|
|
558
|
+
);
|
|
559
|
+
}
|
|
105
560
|
};
|
|
106
561
|
|
|
107
562
|
// ../task-manager/dist/index.js
|
|
@@ -138,29 +593,141 @@ var TaskManager = class {
|
|
|
138
593
|
await this.taskProvider.updateTask(updatedTask);
|
|
139
594
|
return updatedTask;
|
|
140
595
|
}
|
|
596
|
+
async createTask(options) {
|
|
597
|
+
return this.taskProvider.createTask(options);
|
|
598
|
+
}
|
|
599
|
+
async lint(fix) {
|
|
600
|
+
return this.taskProvider.lint(fix);
|
|
601
|
+
}
|
|
141
602
|
};
|
|
142
603
|
|
|
143
|
-
// ../task-manager/dist/task-manager.
|
|
604
|
+
// ../task-manager/dist/task-manager.types.js
|
|
144
605
|
init_esm_shims();
|
|
145
606
|
|
|
146
|
-
//
|
|
607
|
+
// src/commands/dashboard.ts
|
|
608
|
+
import { TaskWebSocketServer } from "@opentask/taskin-task-server-ws";
|
|
609
|
+
|
|
610
|
+
// ../utils/dist/index.js
|
|
147
611
|
init_esm_shims();
|
|
148
612
|
|
|
149
|
-
//
|
|
150
|
-
|
|
613
|
+
// ../utils/dist/security.js
|
|
614
|
+
init_esm_shims();
|
|
615
|
+
import { z } from "zod";
|
|
616
|
+
var HostSchema = z.string().refine((host) => {
|
|
617
|
+
if (!host || host.length === 0)
|
|
618
|
+
return false;
|
|
619
|
+
if (host === "localhost")
|
|
620
|
+
return true;
|
|
621
|
+
const parts = host.split(".");
|
|
622
|
+
const allNumeric = parts.every((p) => /^\d+$/.test(p));
|
|
623
|
+
if (allNumeric) {
|
|
624
|
+
if (parts.length !== 4)
|
|
625
|
+
return false;
|
|
626
|
+
return parts.every((part) => {
|
|
627
|
+
const num = parseInt(part, 10);
|
|
628
|
+
return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
632
|
+
return hostnameRegex.test(host);
|
|
633
|
+
}, {
|
|
634
|
+
message: "Invalid host. Must be localhost, a valid IPv4 address, or hostname."
|
|
635
|
+
});
|
|
636
|
+
var PortSchema = z.union([
|
|
637
|
+
z.number().int().min(1).max(65535),
|
|
638
|
+
z.string().regex(/^\d+$/).transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1).max(65535))
|
|
639
|
+
]);
|
|
640
|
+
var WebSocketUrlSchema = z.string().refine((url) => {
|
|
641
|
+
try {
|
|
642
|
+
const parsed = new URL(url);
|
|
643
|
+
return parsed.protocol === "ws:" || parsed.protocol === "wss:";
|
|
644
|
+
} catch {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
}, { message: "Invalid WebSocket URL. Must use ws:// or wss:// protocol." });
|
|
648
|
+
var TaskIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9_-]+$/, {
|
|
649
|
+
message: "Task ID must contain only alphanumeric characters, hyphens, and underscores."
|
|
650
|
+
});
|
|
651
|
+
var UserIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9._-]+$/, {
|
|
652
|
+
message: "User ID must contain only alphanumeric characters, dots, hyphens, and underscores."
|
|
653
|
+
});
|
|
654
|
+
var EmailSchema = z.string().email().max(254);
|
|
655
|
+
var SafePathSchema = z.string().refine((filePath) => {
|
|
656
|
+
if (!filePath || filePath.length === 0)
|
|
657
|
+
return false;
|
|
658
|
+
const dangerousPatterns = [
|
|
659
|
+
/\.\./,
|
|
660
|
+
// Parent directory (..)
|
|
661
|
+
/~\//,
|
|
662
|
+
// Home directory
|
|
663
|
+
/^\//,
|
|
664
|
+
// Absolute path
|
|
665
|
+
/^[A-Za-z]:\\/
|
|
666
|
+
// Windows absolute path
|
|
667
|
+
];
|
|
668
|
+
return !dangerousPatterns.some((pattern) => pattern.test(filePath));
|
|
669
|
+
}, {
|
|
670
|
+
message: "Invalid path. Must be a relative path without traversal patterns."
|
|
671
|
+
});
|
|
672
|
+
var DashboardOptionsSchema = z.object({
|
|
673
|
+
host: HostSchema.optional(),
|
|
674
|
+
port: PortSchema.optional(),
|
|
675
|
+
wsPort: PortSchema.optional()
|
|
676
|
+
});
|
|
677
|
+
function isValidHost(host) {
|
|
678
|
+
return HostSchema.safeParse(host).success;
|
|
679
|
+
}
|
|
680
|
+
function isValidPort(port) {
|
|
681
|
+
return PortSchema.safeParse(port).success;
|
|
682
|
+
}
|
|
683
|
+
function escapeHtml(text) {
|
|
684
|
+
if (!text || typeof text !== "string") {
|
|
685
|
+
return "";
|
|
686
|
+
}
|
|
687
|
+
const htmlEscapeMap = {
|
|
688
|
+
"&": "&",
|
|
689
|
+
"<": "<",
|
|
690
|
+
">": ">",
|
|
691
|
+
'"': """,
|
|
692
|
+
"'": "'",
|
|
693
|
+
"/": "/"
|
|
694
|
+
};
|
|
695
|
+
return text.replace(/[&<>"'/]/g, (char) => htmlEscapeMap[char]);
|
|
696
|
+
}
|
|
151
697
|
|
|
152
|
-
//
|
|
698
|
+
// ../utils/dist/ui.js
|
|
153
699
|
init_esm_shims();
|
|
154
700
|
import chalk from "chalk";
|
|
155
701
|
var colors = {
|
|
156
|
-
|
|
702
|
+
primary: chalk.blue,
|
|
703
|
+
secondary: chalk.gray,
|
|
157
704
|
success: chalk.green,
|
|
158
705
|
warning: chalk.yellow,
|
|
159
706
|
error: chalk.red,
|
|
160
|
-
|
|
161
|
-
|
|
707
|
+
info: chalk.cyan,
|
|
708
|
+
highlight: chalk.magenta,
|
|
162
709
|
normal: chalk.white
|
|
163
710
|
};
|
|
711
|
+
|
|
712
|
+
// src/commands/dashboard.ts
|
|
713
|
+
import chalk3 from "chalk";
|
|
714
|
+
import express from "express";
|
|
715
|
+
import { createServer } from "http";
|
|
716
|
+
import path5 from "path";
|
|
717
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
718
|
+
|
|
719
|
+
// src/lib/colors.ts
|
|
720
|
+
init_esm_shims();
|
|
721
|
+
import chalk2 from "chalk";
|
|
722
|
+
var colors2 = {
|
|
723
|
+
info: chalk2.cyan,
|
|
724
|
+
success: chalk2.green,
|
|
725
|
+
warning: chalk2.yellow,
|
|
726
|
+
error: chalk2.red,
|
|
727
|
+
highlight: chalk2.bold.white,
|
|
728
|
+
secondary: chalk2.gray,
|
|
729
|
+
normal: chalk2.white
|
|
730
|
+
};
|
|
164
731
|
var icons = {
|
|
165
732
|
rocket: "\u{1F680}",
|
|
166
733
|
list: "\u{1F4CA}",
|
|
@@ -172,27 +739,27 @@ var icons = {
|
|
|
172
739
|
};
|
|
173
740
|
function printHeader(title, icon) {
|
|
174
741
|
console.log();
|
|
175
|
-
console.log(
|
|
176
|
-
console.log(
|
|
177
|
-
console.log(
|
|
742
|
+
console.log(colors2.highlight("\u2550".repeat(60)));
|
|
743
|
+
console.log(colors2.highlight(`${icon} ${title}`));
|
|
744
|
+
console.log(colors2.highlight("\u2550".repeat(60)));
|
|
178
745
|
console.log();
|
|
179
746
|
}
|
|
180
747
|
function success(message) {
|
|
181
|
-
console.log(
|
|
748
|
+
console.log(colors2.success(`\u2713 ${message}`));
|
|
182
749
|
}
|
|
183
750
|
function error(message) {
|
|
184
|
-
console.error(
|
|
751
|
+
console.error(colors2.error(`\u2717 ${message}`));
|
|
185
752
|
}
|
|
186
753
|
function info(message) {
|
|
187
|
-
console.log(
|
|
754
|
+
console.log(colors2.info(`\u2139 ${message}`));
|
|
188
755
|
}
|
|
189
756
|
|
|
190
757
|
// src/lib/project-check.ts
|
|
191
758
|
init_esm_shims();
|
|
192
759
|
import { existsSync } from "fs";
|
|
193
|
-
import
|
|
760
|
+
import path4 from "path";
|
|
194
761
|
function isTaskinProject(cwd = process.cwd()) {
|
|
195
|
-
return existsSync(
|
|
762
|
+
return existsSync(path4.join(cwd, ".taskin.json"));
|
|
196
763
|
}
|
|
197
764
|
function requireTaskinProject(cwd = process.cwd()) {
|
|
198
765
|
if (!isTaskinProject(cwd)) {
|
|
@@ -203,35 +770,6 @@ function requireTaskinProject(cwd = process.cwd()) {
|
|
|
203
770
|
}
|
|
204
771
|
}
|
|
205
772
|
|
|
206
|
-
// src/lib/sound-player.ts
|
|
207
|
-
init_esm_shims();
|
|
208
|
-
import { existsSync as existsSync2 } from "fs";
|
|
209
|
-
import path4 from "path";
|
|
210
|
-
import player from "play-sound";
|
|
211
|
-
var soundPlayer = player({});
|
|
212
|
-
function playSound(soundName) {
|
|
213
|
-
try {
|
|
214
|
-
const possiblePaths = [
|
|
215
|
-
// In development (from src)
|
|
216
|
-
path4.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
|
|
217
|
-
// In production (relative to dist)
|
|
218
|
-
path4.join(__dirname, "..", "sounds", `${soundName}.mp3`),
|
|
219
|
-
// Custom sound in project root
|
|
220
|
-
path4.join(process.cwd(), ".taskin", `${soundName}.mp3`)
|
|
221
|
-
];
|
|
222
|
-
const soundPath = possiblePaths.find((p) => existsSync2(p));
|
|
223
|
-
if (!soundPath) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
soundPlayer.play(soundPath, (err) => {
|
|
227
|
-
if (err) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
} catch {
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
773
|
// src/commands/define-command/index.ts
|
|
236
774
|
init_esm_shims();
|
|
237
775
|
|
|
@@ -262,6 +800,233 @@ var defineCommand = (config) => {
|
|
|
262
800
|
// src/commands/define-command/define-command.types.ts
|
|
263
801
|
init_esm_shims();
|
|
264
802
|
|
|
803
|
+
// src/commands/dashboard.ts
|
|
804
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
805
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
806
|
+
var dashboardCommand = defineCommand({
|
|
807
|
+
name: "dashboard",
|
|
808
|
+
description: "\u{1F4CA} Start the Taskin dashboard with WebSocket server",
|
|
809
|
+
alias: "dash",
|
|
810
|
+
options: [
|
|
811
|
+
{
|
|
812
|
+
flags: "-p, --port <port>",
|
|
813
|
+
description: "Vite dev server port",
|
|
814
|
+
defaultValue: "5173"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
flags: "-w, --ws-port <port>",
|
|
818
|
+
description: "WebSocket server port",
|
|
819
|
+
defaultValue: "3001"
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
flags: "-h, --host <host>",
|
|
823
|
+
description: "Host to bind servers",
|
|
824
|
+
defaultValue: "localhost"
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
flags: "-o, --open",
|
|
828
|
+
description: "Open browser automatically"
|
|
829
|
+
}
|
|
830
|
+
],
|
|
831
|
+
handler: async (options) => {
|
|
832
|
+
await startDashboard(options);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
async function startDashboard(options) {
|
|
836
|
+
requireTaskinProject();
|
|
837
|
+
const host = options.host || "localhost";
|
|
838
|
+
if (!isValidHost(host)) {
|
|
839
|
+
error("Security validation failed");
|
|
840
|
+
error(
|
|
841
|
+
`Invalid host: ${host}. Must be localhost, a valid IPv4 address, or hostname.`
|
|
842
|
+
);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
if (typeof options.port === "string" && !isValidPort(options.port)) {
|
|
846
|
+
error("Security validation failed");
|
|
847
|
+
error(`Invalid port: ${options.port}. Must be between 1 and 65535.`);
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
if (typeof options.wsPort === "string" && !isValidPort(options.wsPort)) {
|
|
851
|
+
error("Security validation failed");
|
|
852
|
+
error(
|
|
853
|
+
`Invalid WebSocket port: ${options.wsPort}. Must be between 1 and 65535.`
|
|
854
|
+
);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
const port = typeof options.port === "string" ? parseInt(options.port, 10) : options.port || 5173;
|
|
858
|
+
const wsPort = typeof options.wsPort === "string" ? parseInt(options.wsPort, 10) : options.wsPort || 3001;
|
|
859
|
+
if (!isValidPort(port)) {
|
|
860
|
+
error("Security validation failed");
|
|
861
|
+
error(`Invalid port: ${port}. Must be between 1 and 65535.`);
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
if (!isValidPort(wsPort)) {
|
|
865
|
+
error("Security validation failed");
|
|
866
|
+
error(`Invalid WebSocket port: ${wsPort}. Must be between 1 and 65535.`);
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
printHeader("Starting Taskin Dashboard", "\u{1F4CA}");
|
|
870
|
+
try {
|
|
871
|
+
info("Initializing task provider...");
|
|
872
|
+
let currentDir = process.cwd();
|
|
873
|
+
let tasksDir = path5.join(currentDir, "TASKS");
|
|
874
|
+
try {
|
|
875
|
+
await import("fs").then((fs3) => fs3.promises.access(tasksDir));
|
|
876
|
+
} catch {
|
|
877
|
+
while (currentDir !== path5.dirname(currentDir)) {
|
|
878
|
+
const workspaceFile = path5.join(currentDir, "pnpm-workspace.yaml");
|
|
879
|
+
try {
|
|
880
|
+
await import("fs").then((fs3) => fs3.promises.access(workspaceFile));
|
|
881
|
+
tasksDir = path5.join(currentDir, "TASKS");
|
|
882
|
+
break;
|
|
883
|
+
} catch {
|
|
884
|
+
currentDir = path5.dirname(currentDir);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
info(`Using tasks directory: ${tasksDir}`);
|
|
889
|
+
const monorepoRoot = path5.dirname(tasksDir);
|
|
890
|
+
const taskinDir = path5.join(monorepoRoot, ".taskin");
|
|
891
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
892
|
+
await userRegistry.load();
|
|
893
|
+
const provider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
894
|
+
const manager = new TaskManager(provider);
|
|
895
|
+
info(`Starting WebSocket server on ${host}:${wsPort}...`);
|
|
896
|
+
const wsServer = new TaskWebSocketServer({
|
|
897
|
+
taskManager: manager,
|
|
898
|
+
taskProvider: provider,
|
|
899
|
+
options: {
|
|
900
|
+
port: wsPort,
|
|
901
|
+
host
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
await wsServer.start();
|
|
905
|
+
success(`\u2713 WebSocket server running on ws://${host}:${wsPort}`);
|
|
906
|
+
info(`Starting dashboard server on http://${host}:${port}...`);
|
|
907
|
+
const isDev = __dirname2.includes("/src/");
|
|
908
|
+
const dashboardDist = isDev ? path5.join(__dirname2, "..", "..", "dashboard-dist") : path5.join(__dirname2, "..", "dashboard-dist");
|
|
909
|
+
const app = express();
|
|
910
|
+
app.disable("x-powered-by");
|
|
911
|
+
app.use((req, res, next) => {
|
|
912
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
913
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
914
|
+
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
915
|
+
res.setHeader(
|
|
916
|
+
"Content-Security-Policy",
|
|
917
|
+
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:;"
|
|
918
|
+
);
|
|
919
|
+
next();
|
|
920
|
+
});
|
|
921
|
+
app.use((req, res, next) => {
|
|
922
|
+
if (req.path === "/" || req.path === "/index.html") {
|
|
923
|
+
import("fs").then(
|
|
924
|
+
(fs3) => fs3.promises.readFile(
|
|
925
|
+
path5.join(dashboardDist, "index.html"),
|
|
926
|
+
"utf-8"
|
|
927
|
+
)
|
|
928
|
+
).then((html) => {
|
|
929
|
+
const safeHost = escapeHtml(host);
|
|
930
|
+
const safeWsPort = escapeHtml(String(wsPort));
|
|
931
|
+
const injectedHtml = html.replace(
|
|
932
|
+
"</head>",
|
|
933
|
+
`<script>window.VITE_WS_URL = 'ws://${safeHost}:${safeWsPort}';</script></head>`
|
|
934
|
+
);
|
|
935
|
+
res.send(injectedHtml);
|
|
936
|
+
}).catch((err) => {
|
|
937
|
+
console.error("Failed to read index.html:", err);
|
|
938
|
+
res.status(500).send("Internal Server Error");
|
|
939
|
+
});
|
|
940
|
+
} else {
|
|
941
|
+
next();
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
app.use(
|
|
945
|
+
express.static(dashboardDist, {
|
|
946
|
+
dotfiles: "deny",
|
|
947
|
+
// Deny access to dotfiles
|
|
948
|
+
index: false,
|
|
949
|
+
// Don't serve index.html here (handled above)
|
|
950
|
+
redirect: false
|
|
951
|
+
// Don't redirect to trailing slash
|
|
952
|
+
})
|
|
953
|
+
);
|
|
954
|
+
app.use((req, res) => {
|
|
955
|
+
res.status(404).send("Not Found");
|
|
956
|
+
});
|
|
957
|
+
const httpServer = createServer(app);
|
|
958
|
+
await new Promise((resolve) => {
|
|
959
|
+
httpServer.listen(port, host, () => {
|
|
960
|
+
resolve();
|
|
961
|
+
});
|
|
962
|
+
});
|
|
963
|
+
success(`\u2713 Dashboard available at http://${host}:${port}`);
|
|
964
|
+
if (options.open) {
|
|
965
|
+
const url = `http://${host}:${port}`;
|
|
966
|
+
await import("child_process").then((cp) => {
|
|
967
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
968
|
+
cp.exec(`${cmd} ${url}`);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
info("");
|
|
972
|
+
info(chalk3.bold("Dashboard Controls:"));
|
|
973
|
+
info(` \u2022 Dashboard: ${chalk3.cyan(`http://${host}:${port}`)}`);
|
|
974
|
+
info(` \u2022 WebSocket: ${chalk3.cyan(`ws://${host}:${wsPort}`)}`);
|
|
975
|
+
info(` \u2022 Press ${chalk3.bold("Ctrl+C")} to stop both servers`);
|
|
976
|
+
info("");
|
|
977
|
+
const cleanup = async () => {
|
|
978
|
+
info("\nShutting down servers...");
|
|
979
|
+
await new Promise((resolve) => {
|
|
980
|
+
httpServer.close(() => resolve());
|
|
981
|
+
});
|
|
982
|
+
await wsServer.stop();
|
|
983
|
+
success("\u2713 Servers stopped");
|
|
984
|
+
process.exit(0);
|
|
985
|
+
};
|
|
986
|
+
process.on("SIGINT", cleanup);
|
|
987
|
+
process.on("SIGTERM", cleanup);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
error("Failed to start dashboard");
|
|
990
|
+
if (err instanceof Error) {
|
|
991
|
+
error(err.message);
|
|
992
|
+
}
|
|
993
|
+
process.exit(1);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/commands/finish.ts
|
|
998
|
+
init_esm_shims();
|
|
999
|
+
import path7 from "path";
|
|
1000
|
+
|
|
1001
|
+
// src/lib/sound-player.ts
|
|
1002
|
+
init_esm_shims();
|
|
1003
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1004
|
+
import path6 from "path";
|
|
1005
|
+
import player from "play-sound";
|
|
1006
|
+
var soundPlayer = player({});
|
|
1007
|
+
function playSound(soundName) {
|
|
1008
|
+
try {
|
|
1009
|
+
const possiblePaths = [
|
|
1010
|
+
// In development (from src)
|
|
1011
|
+
path6.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
|
|
1012
|
+
// In production (relative to dist)
|
|
1013
|
+
path6.join(__dirname, "..", "sounds", `${soundName}.mp3`),
|
|
1014
|
+
// Custom sound in project root
|
|
1015
|
+
path6.join(process.cwd(), ".taskin", `${soundName}.mp3`)
|
|
1016
|
+
];
|
|
1017
|
+
const soundPath = possiblePaths.find((p) => existsSync2(p));
|
|
1018
|
+
if (!soundPath) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
soundPlayer.play(soundPath, (err) => {
|
|
1022
|
+
if (err) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
265
1030
|
// src/commands/finish.ts
|
|
266
1031
|
var finishCommand = defineCommand({
|
|
267
1032
|
name: "finish <task-id>",
|
|
@@ -285,8 +1050,12 @@ async function finishTask(taskId, options) {
|
|
|
285
1050
|
requireTaskinProject();
|
|
286
1051
|
printHeader(`Finishing Task ${taskId}`, "\u2705");
|
|
287
1052
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
288
|
-
const tasksDir =
|
|
289
|
-
const
|
|
1053
|
+
const tasksDir = path7.join(process.cwd(), "TASKS");
|
|
1054
|
+
const monorepoRoot = path7.dirname(tasksDir);
|
|
1055
|
+
const taskinDir = path7.join(monorepoRoot, ".taskin");
|
|
1056
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
1057
|
+
await userRegistry.load();
|
|
1058
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
290
1059
|
const taskManager = new TaskManager(taskProvider);
|
|
291
1060
|
const task = await taskProvider.findTask(normalizedId);
|
|
292
1061
|
if (!task) {
|
|
@@ -311,14 +1080,14 @@ async function finishTask(taskId, options) {
|
|
|
311
1080
|
info("Suggested commit message:");
|
|
312
1081
|
const commitType = task.type || "feat";
|
|
313
1082
|
console.log(
|
|
314
|
-
|
|
1083
|
+
colors2.highlight(` ${commitType}(task-${normalizedId}): ${task.title}`)
|
|
315
1084
|
);
|
|
316
1085
|
console.log();
|
|
317
1086
|
info("Next steps:");
|
|
318
|
-
console.log(
|
|
319
|
-
console.log(
|
|
320
|
-
console.log(
|
|
321
|
-
console.log(
|
|
1087
|
+
console.log(colors2.secondary(" 1. Review your changes"));
|
|
1088
|
+
console.log(colors2.secondary(" 2. Commit: git add . && git commit"));
|
|
1089
|
+
console.log(colors2.secondary(" 3. Push: git push"));
|
|
1090
|
+
console.log(colors2.secondary(" 4. Create a Pull Request"));
|
|
322
1091
|
console.log();
|
|
323
1092
|
success("Great work! \u{1F680}");
|
|
324
1093
|
console.log();
|
|
@@ -572,7 +1341,7 @@ async function initializeTaskin(options) {
|
|
|
572
1341
|
let providerId;
|
|
573
1342
|
if (options.provider) {
|
|
574
1343
|
providerId = options.provider;
|
|
575
|
-
info(`Using provider from command line: ${
|
|
1344
|
+
info(`Using provider from command line: ${colors2.highlight(providerId)}`);
|
|
576
1345
|
} else if (process.env.CI === "true") {
|
|
577
1346
|
providerId = "fs";
|
|
578
1347
|
info("CI environment detected, using default provider: fs");
|
|
@@ -594,7 +1363,7 @@ async function initializeTaskin(options) {
|
|
|
594
1363
|
process.exit(1);
|
|
595
1364
|
}
|
|
596
1365
|
console.log();
|
|
597
|
-
info(`Setting up task provider: ${
|
|
1366
|
+
info(`Setting up task provider: ${colors2.highlight(selectedProvider.name)}`);
|
|
598
1367
|
console.log();
|
|
599
1368
|
const bundledProviders = ["fs"];
|
|
600
1369
|
if (!bundledProviders.includes(selectedProvider.id)) {
|
|
@@ -610,7 +1379,7 @@ async function initializeTaskin(options) {
|
|
|
610
1379
|
};
|
|
611
1380
|
info("Creating configuration file...");
|
|
612
1381
|
writeFileSync(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
613
|
-
success(`\u2713 Created ${
|
|
1382
|
+
success(`\u2713 Created ${colors2.highlight(".taskin.json")}`);
|
|
614
1383
|
const gitignorePath = join(cwd, ".gitignore");
|
|
615
1384
|
if (existsSync4(gitignorePath)) {
|
|
616
1385
|
const gitignoreContent = __require("fs").readFileSync(gitignorePath, "utf-8");
|
|
@@ -631,13 +1400,13 @@ async function initializeTaskin(options) {
|
|
|
631
1400
|
success("\u{1F389} Taskin initialized successfully!");
|
|
632
1401
|
console.log();
|
|
633
1402
|
info("Next steps:");
|
|
634
|
-
console.log(
|
|
1403
|
+
console.log(colors2.secondary(" 1. Run: taskin list"));
|
|
635
1404
|
console.log(
|
|
636
|
-
|
|
1405
|
+
colors2.secondary(
|
|
637
1406
|
selectedProvider.id === "fs" ? " 2. Create a new task: taskin new (interactive mode)" : ` 2. Tasks will be synced with ${selectedProvider.name}`
|
|
638
1407
|
)
|
|
639
1408
|
);
|
|
640
|
-
console.log(
|
|
1409
|
+
console.log(colors2.secondary(" 3. Start working: taskin start <task-id>"));
|
|
641
1410
|
console.log();
|
|
642
1411
|
info("For more information, run: taskin --help");
|
|
643
1412
|
console.log();
|
|
@@ -671,9 +1440,9 @@ async function setupFileSystemProvider(cwd) {
|
|
|
671
1440
|
if (!existsSync4(tasksDir)) {
|
|
672
1441
|
info(`Creating TASKS directory...`);
|
|
673
1442
|
mkdirSync(tasksDir, { recursive: true });
|
|
674
|
-
success(`\u2713 Created ${
|
|
1443
|
+
success(`\u2713 Created ${colors2.highlight("TASKS/")} directory`);
|
|
675
1444
|
} else {
|
|
676
|
-
info(`${
|
|
1445
|
+
info(`${colors2.highlight("TASKS/")} directory already exists`);
|
|
677
1446
|
success("\u2713 Directory is ready to use");
|
|
678
1447
|
}
|
|
679
1448
|
const sampleTaskFile = join(tasksDir, "task-001-setup-project.md");
|
|
@@ -701,7 +1470,7 @@ You can edit or delete this file. Use \`taskin list\` to see all tasks.
|
|
|
701
1470
|
`;
|
|
702
1471
|
writeFileSync(sampleTaskFile, sampleTask, "utf-8");
|
|
703
1472
|
success(
|
|
704
|
-
`\u2713 Created sample task ${
|
|
1473
|
+
`\u2713 Created sample task ${colors2.highlight("task-001-setup-project.md")}`
|
|
705
1474
|
);
|
|
706
1475
|
}
|
|
707
1476
|
return {
|
|
@@ -718,8 +1487,8 @@ init_esm_shims();
|
|
|
718
1487
|
|
|
719
1488
|
// src/lib/file-system-task-linter/file-system-task-linter.ts
|
|
720
1489
|
init_esm_shims();
|
|
721
|
-
import
|
|
722
|
-
import { readdir, readFile } from "fs/promises";
|
|
1490
|
+
import chalk4 from "chalk";
|
|
1491
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
723
1492
|
import { join as join2 } from "path";
|
|
724
1493
|
var VALID_STATUSES = ["pending", "in-progress", "done", "blocked"];
|
|
725
1494
|
var VALID_TYPES = ["feat", "fix", "chore", "docs", "refactor", "test"];
|
|
@@ -848,7 +1617,7 @@ var FileSystemTaskLinter = class {
|
|
|
848
1617
|
errors.push(fileNameError);
|
|
849
1618
|
}
|
|
850
1619
|
try {
|
|
851
|
-
const content = await
|
|
1620
|
+
const content = await readFile2(filePath, "utf-8");
|
|
852
1621
|
this.errors = [];
|
|
853
1622
|
this.validateContent(fileName, content);
|
|
854
1623
|
errors.push(...this.errors);
|
|
@@ -874,7 +1643,7 @@ var FileSystemTaskLinter = class {
|
|
|
874
1643
|
if (fileNameError) {
|
|
875
1644
|
this.addError(file, fileNameError.message, fileNameError.severity);
|
|
876
1645
|
}
|
|
877
|
-
const content = await
|
|
1646
|
+
const content = await readFile2(join2(tasksDir, file), "utf-8");
|
|
878
1647
|
this.validateContent(file, content);
|
|
879
1648
|
}
|
|
880
1649
|
const errors = this.errors.filter((e) => e.severity === "error");
|
|
@@ -892,13 +1661,13 @@ var FileSystemTaskLinter = class {
|
|
|
892
1661
|
static printResults(result) {
|
|
893
1662
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
894
1663
|
console.log(
|
|
895
|
-
|
|
1664
|
+
chalk4.green(`\u2705 All ${result.filesChecked} task files are valid!
|
|
896
1665
|
`)
|
|
897
1666
|
);
|
|
898
1667
|
return;
|
|
899
1668
|
}
|
|
900
1669
|
console.log(
|
|
901
|
-
|
|
1670
|
+
chalk4.bold(
|
|
902
1671
|
`
|
|
903
1672
|
\u{1F4CA} Validation Results (${result.filesChecked} files checked):
|
|
904
1673
|
`
|
|
@@ -912,19 +1681,19 @@ var FileSystemTaskLinter = class {
|
|
|
912
1681
|
errorsByFile.get(error2.file).push(error2);
|
|
913
1682
|
});
|
|
914
1683
|
for (const [file, fileErrors] of errorsByFile) {
|
|
915
|
-
console.log(
|
|
1684
|
+
console.log(chalk4.cyan(`
|
|
916
1685
|
\u{1F4C4} ${file}`));
|
|
917
1686
|
for (const error2 of fileErrors) {
|
|
918
|
-
const icon = error2.severity === "error" ?
|
|
1687
|
+
const icon = error2.severity === "error" ? chalk4.red("\u274C") : chalk4.yellow("\u26A0\uFE0F");
|
|
919
1688
|
const location = error2.line ? `:${error2.line}` : "";
|
|
920
1689
|
console.log(` ${icon} ${error2.message}${location}`);
|
|
921
1690
|
}
|
|
922
1691
|
}
|
|
923
1692
|
console.log("\n" + "\u2500".repeat(60));
|
|
924
1693
|
console.log(
|
|
925
|
-
|
|
1694
|
+
chalk4.bold(
|
|
926
1695
|
`
|
|
927
|
-
\u{1F4CA} Summary: ${
|
|
1696
|
+
\u{1F4CA} Summary: ${chalk4.red(result.errors.length + " error(s)")}, ${chalk4.yellow(result.warnings.length + " warning(s)")}
|
|
928
1697
|
`
|
|
929
1698
|
)
|
|
930
1699
|
);
|
|
@@ -1013,7 +1782,7 @@ async function executeLint(options) {
|
|
|
1013
1782
|
|
|
1014
1783
|
// src/commands/list.ts
|
|
1015
1784
|
init_esm_shims();
|
|
1016
|
-
import
|
|
1785
|
+
import path8 from "path";
|
|
1017
1786
|
var listCommand = defineCommand({
|
|
1018
1787
|
name: "list [filter]",
|
|
1019
1788
|
description: "\u{1F4CA} List all tasks in the project",
|
|
@@ -1039,11 +1808,15 @@ var listCommand = defineCommand({
|
|
|
1039
1808
|
async function listTasks(filter, options) {
|
|
1040
1809
|
requireTaskinProject();
|
|
1041
1810
|
printHeader("Task List", "\u{1F4CA}");
|
|
1042
|
-
const tasksDir =
|
|
1043
|
-
const
|
|
1811
|
+
const tasksDir = path8.join(process.cwd(), "TASKS");
|
|
1812
|
+
const monorepoRoot = path8.dirname(tasksDir);
|
|
1813
|
+
const taskinDir = path8.join(monorepoRoot, ".taskin");
|
|
1814
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
1815
|
+
await userRegistry.load();
|
|
1816
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
1044
1817
|
const tasks = await taskProvider.getAllTasks();
|
|
1045
1818
|
if (tasks.length === 0) {
|
|
1046
|
-
console.log(
|
|
1819
|
+
console.log(colors2.warning("No tasks found in TASKS/ directory"));
|
|
1047
1820
|
return;
|
|
1048
1821
|
}
|
|
1049
1822
|
let filteredTasks = tasks;
|
|
@@ -1065,24 +1838,24 @@ async function listTasks(filter, options) {
|
|
|
1065
1838
|
);
|
|
1066
1839
|
}
|
|
1067
1840
|
if (filteredTasks.length === 0) {
|
|
1068
|
-
console.log(
|
|
1841
|
+
console.log(colors2.warning("No tasks match the filters"));
|
|
1069
1842
|
return;
|
|
1070
1843
|
}
|
|
1071
1844
|
console.log(
|
|
1072
|
-
|
|
1845
|
+
colors2.highlight(
|
|
1073
1846
|
`${"ID".padEnd(15)} ${"Status".padEnd(15)} ${"Type".padEnd(12)} ${"User".padEnd(15)} ${"Title"}`
|
|
1074
1847
|
)
|
|
1075
1848
|
);
|
|
1076
|
-
console.log(
|
|
1849
|
+
console.log(colors2.secondary("\u2500".repeat(100)));
|
|
1077
1850
|
filteredTasks.forEach((task) => {
|
|
1078
1851
|
const statusColor = getStatusColor(task.status);
|
|
1079
1852
|
const typeColor = getTypeColor(task.type);
|
|
1080
1853
|
console.log(
|
|
1081
|
-
`${
|
|
1854
|
+
`${colors2.info(task.id.padEnd(15))} ${statusColor(task.status.padEnd(15))} ${typeColor(task.type.padEnd(12))} ${colors2.secondary((task.userId || "unknown").padEnd(15))} ${colors2.normal(task.title)}`
|
|
1082
1855
|
);
|
|
1083
1856
|
});
|
|
1084
1857
|
console.log();
|
|
1085
|
-
console.log(
|
|
1858
|
+
console.log(colors2.secondary("\u2500".repeat(100)));
|
|
1086
1859
|
const statusCounts = {
|
|
1087
1860
|
pending: filteredTasks.filter((t) => t.status === "pending").length,
|
|
1088
1861
|
"in-progress": filteredTasks.filter((t) => t.status === "in-progress").length,
|
|
@@ -1090,7 +1863,7 @@ async function listTasks(filter, options) {
|
|
|
1090
1863
|
blocked: filteredTasks.filter((t) => t.status === "blocked").length
|
|
1091
1864
|
};
|
|
1092
1865
|
console.log(
|
|
1093
|
-
|
|
1866
|
+
colors2.info(
|
|
1094
1867
|
`\u{1F4CA} Total: ${filteredTasks.length} tasks | \u23F3 Pending: ${statusCounts.pending} | \u{1F680} In Progress: ${statusCounts["in-progress"]} | \u2705 Done: ${statusCounts.done} | \u{1F6AB} Blocked: ${statusCounts.blocked}`
|
|
1095
1868
|
)
|
|
1096
1869
|
);
|
|
@@ -1099,33 +1872,125 @@ async function listTasks(filter, options) {
|
|
|
1099
1872
|
function getStatusColor(status) {
|
|
1100
1873
|
switch (status) {
|
|
1101
1874
|
case "pending":
|
|
1102
|
-
return
|
|
1875
|
+
return colors2.secondary;
|
|
1103
1876
|
case "in-progress":
|
|
1104
|
-
return
|
|
1877
|
+
return colors2.info;
|
|
1105
1878
|
case "done":
|
|
1106
|
-
return
|
|
1879
|
+
return colors2.success;
|
|
1107
1880
|
case "blocked":
|
|
1108
|
-
return
|
|
1881
|
+
return colors2.error;
|
|
1109
1882
|
default:
|
|
1110
|
-
return
|
|
1883
|
+
return colors2.normal;
|
|
1111
1884
|
}
|
|
1112
1885
|
}
|
|
1113
1886
|
function getTypeColor(type) {
|
|
1114
1887
|
switch (type) {
|
|
1115
1888
|
case "feat":
|
|
1116
|
-
return
|
|
1889
|
+
return colors2.success;
|
|
1117
1890
|
case "fix":
|
|
1118
|
-
return
|
|
1891
|
+
return colors2.error;
|
|
1119
1892
|
case "refactor":
|
|
1120
|
-
return
|
|
1893
|
+
return colors2.warning;
|
|
1121
1894
|
case "docs":
|
|
1122
|
-
return
|
|
1895
|
+
return colors2.info;
|
|
1123
1896
|
case "test":
|
|
1124
|
-
return
|
|
1897
|
+
return colors2.secondary;
|
|
1125
1898
|
case "chore":
|
|
1126
|
-
return
|
|
1899
|
+
return colors2.normal;
|
|
1127
1900
|
default:
|
|
1128
|
-
return
|
|
1901
|
+
return colors2.normal;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// src/commands/mcp-server.ts
|
|
1906
|
+
init_esm_shims();
|
|
1907
|
+
import { TaskMCPServer } from "@opentask/taskin-task-server-mcp";
|
|
1908
|
+
import chalk5 from "chalk";
|
|
1909
|
+
import path9 from "path";
|
|
1910
|
+
var mcpServerCommand = defineCommand({
|
|
1911
|
+
name: "mcp-server",
|
|
1912
|
+
description: "\u{1F916} Start Model Context Protocol server for LLM integration",
|
|
1913
|
+
alias: "mcp",
|
|
1914
|
+
options: [
|
|
1915
|
+
{
|
|
1916
|
+
flags: "-t, --transport <type>",
|
|
1917
|
+
description: "Transport type (stdio or sse)",
|
|
1918
|
+
defaultValue: "stdio"
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
flags: "-d, --debug",
|
|
1922
|
+
description: "Enable debug logging"
|
|
1923
|
+
}
|
|
1924
|
+
],
|
|
1925
|
+
handler: async (options) => {
|
|
1926
|
+
await startMCPServer(options);
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
async function startMCPServer(options) {
|
|
1930
|
+
requireTaskinProject();
|
|
1931
|
+
const transport = options.transport || "stdio";
|
|
1932
|
+
const debug = options.debug || false;
|
|
1933
|
+
printHeader("Starting MCP Server", "\u{1F916}");
|
|
1934
|
+
try {
|
|
1935
|
+
info("Initializing task manager...");
|
|
1936
|
+
const tasksDir = path9.join(process.cwd(), "TASKS");
|
|
1937
|
+
const monorepoRoot = path9.dirname(tasksDir);
|
|
1938
|
+
const taskinDir = path9.join(monorepoRoot, ".taskin");
|
|
1939
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
1940
|
+
await userRegistry.load();
|
|
1941
|
+
const provider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
1942
|
+
const manager = new TaskManager(provider);
|
|
1943
|
+
info(`Creating MCP server with ${transport} transport...`);
|
|
1944
|
+
const mcpServer = new TaskMCPServer({
|
|
1945
|
+
taskManager: manager,
|
|
1946
|
+
name: "taskin-mcp-server",
|
|
1947
|
+
version: "1.0.0",
|
|
1948
|
+
debug
|
|
1949
|
+
});
|
|
1950
|
+
info("Starting MCP server...");
|
|
1951
|
+
await mcpServer.connect({ transport });
|
|
1952
|
+
success("\u2713 MCP server started successfully");
|
|
1953
|
+
info("");
|
|
1954
|
+
info(chalk5.bold("Server Information:"));
|
|
1955
|
+
info(` \u2022 Transport: ${chalk5.cyan(transport)}`);
|
|
1956
|
+
info(` \u2022 Debug: ${chalk5.cyan(debug ? "enabled" : "disabled")}`);
|
|
1957
|
+
info("");
|
|
1958
|
+
info(chalk5.bold("Available Tools:"));
|
|
1959
|
+
info(` \u2022 ${chalk5.green("start_task")} - Start working on a task`);
|
|
1960
|
+
info(` \u2022 ${chalk5.green("finish_task")} - Mark a task as finished`);
|
|
1961
|
+
info("");
|
|
1962
|
+
info(chalk5.bold("Available Prompts:"));
|
|
1963
|
+
info(
|
|
1964
|
+
` \u2022 ${chalk5.green("start-task-workflow")} - Guide for starting tasks`
|
|
1965
|
+
);
|
|
1966
|
+
info(
|
|
1967
|
+
` \u2022 ${chalk5.green("finish-task-workflow")} - Guide for finishing tasks`
|
|
1968
|
+
);
|
|
1969
|
+
info(` \u2022 ${chalk5.green("task-summary")} - Get task summary and insights`);
|
|
1970
|
+
info("");
|
|
1971
|
+
info(chalk5.bold("Available Resources:"));
|
|
1972
|
+
info(` \u2022 ${chalk5.green("taskin://tasks")} - Access all tasks`);
|
|
1973
|
+
info("");
|
|
1974
|
+
info(`Press ${chalk5.bold("Ctrl+C")} to stop the server`);
|
|
1975
|
+
info("");
|
|
1976
|
+
const cleanup = async () => {
|
|
1977
|
+
info("\nShutting down MCP server...");
|
|
1978
|
+
success("\u2713 Server stopped");
|
|
1979
|
+
process.exit(0);
|
|
1980
|
+
};
|
|
1981
|
+
process.on("SIGINT", cleanup);
|
|
1982
|
+
process.on("SIGTERM", cleanup);
|
|
1983
|
+
await new Promise(() => {
|
|
1984
|
+
});
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
error("Failed to start MCP server");
|
|
1987
|
+
if (err instanceof Error) {
|
|
1988
|
+
error(err.message);
|
|
1989
|
+
if (debug) {
|
|
1990
|
+
console.error(err.stack);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
process.exit(1);
|
|
1129
1994
|
}
|
|
1130
1995
|
}
|
|
1131
1996
|
|
|
@@ -1133,7 +1998,7 @@ function getTypeColor(type) {
|
|
|
1133
1998
|
init_esm_shims();
|
|
1134
1999
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1135
2000
|
import inquirer2 from "inquirer";
|
|
1136
|
-
import
|
|
2001
|
+
import path10 from "path";
|
|
1137
2002
|
var createCommand = defineCommand({
|
|
1138
2003
|
name: "new",
|
|
1139
2004
|
description: "\u2795 Create a new task",
|
|
@@ -1225,11 +2090,15 @@ async function createTask(options) {
|
|
|
1225
2090
|
);
|
|
1226
2091
|
return;
|
|
1227
2092
|
}
|
|
1228
|
-
const tasksDir =
|
|
2093
|
+
const tasksDir = path10.join(process.cwd(), "TASKS");
|
|
1229
2094
|
if (!existsSync5(tasksDir)) {
|
|
1230
2095
|
mkdirSync2(tasksDir, { recursive: true });
|
|
1231
2096
|
}
|
|
1232
|
-
const
|
|
2097
|
+
const monorepoRoot = path10.dirname(tasksDir);
|
|
2098
|
+
const taskinDir = path10.join(monorepoRoot, ".taskin");
|
|
2099
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
2100
|
+
await userRegistry.load();
|
|
2101
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
1233
2102
|
const allTasks = await taskProvider.getAllTasks();
|
|
1234
2103
|
const taskNumbers = allTasks.map((task) => {
|
|
1235
2104
|
const match = task.id.match(/^(\d+)$/);
|
|
@@ -1239,7 +2108,7 @@ async function createTask(options) {
|
|
|
1239
2108
|
const taskId = String(nextNumber).padStart(3, "0");
|
|
1240
2109
|
const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1241
2110
|
const fileName = `task-${taskId}-${titleSlug}.md`;
|
|
1242
|
-
const filePath =
|
|
2111
|
+
const filePath = path10.join(tasksDir, fileName);
|
|
1243
2112
|
if (existsSync5(filePath)) {
|
|
1244
2113
|
error(`Task file already exists: ${fileName}`);
|
|
1245
2114
|
return;
|
|
@@ -1254,14 +2123,14 @@ async function createTask(options) {
|
|
|
1254
2123
|
writeFileSync2(filePath, taskContent, "utf-8");
|
|
1255
2124
|
console.log();
|
|
1256
2125
|
success(`Task ${taskId} created successfully!`);
|
|
1257
|
-
console.log(
|
|
1258
|
-
console.log(
|
|
2126
|
+
console.log(colors2.secondary(`\u{1F4C4} File: ${fileName}`));
|
|
2127
|
+
console.log(colors2.secondary(`\u{1F4C1} Path: ${filePath}`));
|
|
1259
2128
|
console.log();
|
|
1260
|
-
console.log(
|
|
1261
|
-
console.log(
|
|
2129
|
+
console.log(colors2.info("Next steps:"));
|
|
2130
|
+
console.log(colors2.normal(` 1. Edit the task file to add more details`));
|
|
1262
2131
|
console.log(
|
|
1263
|
-
|
|
1264
|
-
` 2. Run ${
|
|
2132
|
+
colors2.normal(
|
|
2133
|
+
` 2. Run ${colors2.highlight("taskin start " + taskId)} to begin working on it`
|
|
1265
2134
|
)
|
|
1266
2135
|
);
|
|
1267
2136
|
console.log();
|
|
@@ -1292,7 +2161,7 @@ Add any relevant notes or links here.
|
|
|
1292
2161
|
// src/commands/pause.ts
|
|
1293
2162
|
init_esm_shims();
|
|
1294
2163
|
import { execSync as execSync2 } from "child_process";
|
|
1295
|
-
import
|
|
2164
|
+
import path11 from "path";
|
|
1296
2165
|
var pauseCommand = defineCommand({
|
|
1297
2166
|
name: "pause <task-id>",
|
|
1298
2167
|
description: "\u23F8\uFE0F Pause work on a task",
|
|
@@ -1319,8 +2188,12 @@ async function pauseTask(taskId, options) {
|
|
|
1319
2188
|
requireTaskinProject();
|
|
1320
2189
|
printHeader(`Pausing Task ${taskId}`, "\u23F8\uFE0F");
|
|
1321
2190
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
1322
|
-
const tasksDir =
|
|
1323
|
-
const
|
|
2191
|
+
const tasksDir = path11.join(process.cwd(), "TASKS");
|
|
2192
|
+
const monorepoRoot = path11.dirname(tasksDir);
|
|
2193
|
+
const taskinDir = path11.join(monorepoRoot, ".taskin");
|
|
2194
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
2195
|
+
await userRegistry.load();
|
|
2196
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
1324
2197
|
const task = await taskProvider.findTask(normalizedId);
|
|
1325
2198
|
if (!task) {
|
|
1326
2199
|
error(`Task ${normalizedId} not found in TASKS/ directory`);
|
|
@@ -1335,12 +2208,12 @@ async function pauseTask(taskId, options) {
|
|
|
1335
2208
|
const commitMessage = options.message || `WIP: task-${normalizedId} - ${task.title}`;
|
|
1336
2209
|
if (options.skipCommit) {
|
|
1337
2210
|
info("Would create commit with message:");
|
|
1338
|
-
console.log(
|
|
2211
|
+
console.log(colors2.highlight(` "${commitMessage}"`));
|
|
1339
2212
|
console.log();
|
|
1340
2213
|
info("Use without --skip-commit to actually commit");
|
|
1341
2214
|
} else {
|
|
1342
2215
|
info("Creating commit...");
|
|
1343
|
-
console.log(
|
|
2216
|
+
console.log(colors2.secondary(` Message: "${commitMessage}"`));
|
|
1344
2217
|
console.log();
|
|
1345
2218
|
try {
|
|
1346
2219
|
execSync2("git add -A", { cwd: process.cwd(), stdio: "ignore" });
|
|
@@ -1357,9 +2230,9 @@ async function pauseTask(taskId, options) {
|
|
|
1357
2230
|
info("Status updated to pending");
|
|
1358
2231
|
console.log();
|
|
1359
2232
|
info("Next steps:");
|
|
1360
|
-
console.log(
|
|
2233
|
+
console.log(colors2.secondary(" 1. Switch to another task"));
|
|
1361
2234
|
console.log(
|
|
1362
|
-
|
|
2235
|
+
colors2.secondary(" 2. Or continue later with the same branch")
|
|
1363
2236
|
);
|
|
1364
2237
|
if (options.sound !== false) {
|
|
1365
2238
|
playSound("stop");
|
|
@@ -1370,7 +2243,7 @@ async function pauseTask(taskId, options) {
|
|
|
1370
2243
|
|
|
1371
2244
|
// src/commands/start.ts
|
|
1372
2245
|
init_esm_shims();
|
|
1373
|
-
import
|
|
2246
|
+
import path12 from "path";
|
|
1374
2247
|
var startCommand = defineCommand({
|
|
1375
2248
|
name: "start <task-id>",
|
|
1376
2249
|
description: "\u{1F680} Start working on a task",
|
|
@@ -1397,8 +2270,12 @@ async function startTask(taskId, _options) {
|
|
|
1397
2270
|
requireTaskinProject();
|
|
1398
2271
|
printHeader(`Starting Task ${taskId}`, "\u{1F680}");
|
|
1399
2272
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
1400
|
-
const tasksDir =
|
|
1401
|
-
const
|
|
2273
|
+
const tasksDir = path12.join(process.cwd(), "TASKS");
|
|
2274
|
+
const monorepoRoot = path12.dirname(tasksDir);
|
|
2275
|
+
const taskinDir = path12.join(monorepoRoot, ".taskin");
|
|
2276
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
2277
|
+
await userRegistry.load();
|
|
2278
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
|
|
1402
2279
|
const taskManager = new TaskManager(taskProvider);
|
|
1403
2280
|
const task = await taskProvider.findTask(normalizedId);
|
|
1404
2281
|
if (!task) {
|
|
@@ -1422,12 +2299,12 @@ async function startTask(taskId, _options) {
|
|
|
1422
2299
|
console.log();
|
|
1423
2300
|
info("Next steps:");
|
|
1424
2301
|
console.log(
|
|
1425
|
-
|
|
2302
|
+
colors2.secondary(
|
|
1426
2303
|
" 1. Create a branch: git checkout -b feat/task-" + normalizedId
|
|
1427
2304
|
)
|
|
1428
2305
|
);
|
|
1429
|
-
console.log(
|
|
1430
|
-
console.log(
|
|
2306
|
+
console.log(colors2.secondary(" 2. Start coding! \u{1F4BB}"));
|
|
2307
|
+
console.log(colors2.secondary(' 3. Use "taskin pause" to save progress'));
|
|
1431
2308
|
console.log();
|
|
1432
2309
|
if (_options.sound !== false) {
|
|
1433
2310
|
playSound("start");
|
|
@@ -1438,20 +2315,20 @@ async function startTask(taskId, _options) {
|
|
|
1438
2315
|
init_esm_shims();
|
|
1439
2316
|
function showCustomHelp() {
|
|
1440
2317
|
printHeader("Taskin - Task Management System", icons.rocket);
|
|
1441
|
-
console.log(
|
|
1442
|
-
console.log(
|
|
2318
|
+
console.log(colors2.info("\u{1F4CB} AVAILABLE COMMANDS"));
|
|
2319
|
+
console.log(colors2.highlight("\u2550".repeat(60)));
|
|
1443
2320
|
console.log();
|
|
1444
2321
|
const commands = [
|
|
1445
2322
|
{
|
|
1446
|
-
name:
|
|
1447
|
-
alias:
|
|
2323
|
+
name: colors2.highlight("taskin init"),
|
|
2324
|
+
alias: colors2.secondary("Alias: setup"),
|
|
1448
2325
|
description: "Initialize Taskin in your project",
|
|
1449
2326
|
examples: ["taskin init", "taskin setup"],
|
|
1450
2327
|
icon: "\u{1F3AF}"
|
|
1451
2328
|
},
|
|
1452
2329
|
{
|
|
1453
|
-
name:
|
|
1454
|
-
alias:
|
|
2330
|
+
name: colors2.highlight("taskin list") + colors2.normal(" [filter]"),
|
|
2331
|
+
alias: colors2.secondary("Alias: ls"),
|
|
1455
2332
|
description: "List all tasks in the project",
|
|
1456
2333
|
examples: [
|
|
1457
2334
|
"taskin list",
|
|
@@ -1462,8 +2339,8 @@ function showCustomHelp() {
|
|
|
1462
2339
|
icon: "\u{1F4CA}"
|
|
1463
2340
|
},
|
|
1464
2341
|
{
|
|
1465
|
-
name:
|
|
1466
|
-
alias:
|
|
2342
|
+
name: colors2.highlight("taskin new"),
|
|
2343
|
+
alias: colors2.secondary("Alias: create"),
|
|
1467
2344
|
description: "Create a new task",
|
|
1468
2345
|
examples: [
|
|
1469
2346
|
'taskin new -t feat -T "Add login" -d "Implement user authentication"',
|
|
@@ -1473,8 +2350,8 @@ function showCustomHelp() {
|
|
|
1473
2350
|
icon: "\u{1F4DD}"
|
|
1474
2351
|
},
|
|
1475
2352
|
{
|
|
1476
|
-
name:
|
|
1477
|
-
alias:
|
|
2353
|
+
name: colors2.highlight("taskin start") + colors2.normal(" <task-id>"),
|
|
2354
|
+
alias: colors2.secondary("Alias: begin"),
|
|
1478
2355
|
description: "Start working on a task",
|
|
1479
2356
|
examples: [
|
|
1480
2357
|
"taskin start 001",
|
|
@@ -1484,22 +2361,22 @@ function showCustomHelp() {
|
|
|
1484
2361
|
icon: "\u{1F680}"
|
|
1485
2362
|
},
|
|
1486
2363
|
{
|
|
1487
|
-
name:
|
|
1488
|
-
alias:
|
|
2364
|
+
name: colors2.highlight("taskin pause") + colors2.normal(" <task-id>"),
|
|
2365
|
+
alias: colors2.secondary("Alias: stop"),
|
|
1489
2366
|
description: "Pause a task (commit without push)",
|
|
1490
2367
|
examples: ["taskin pause 001", 'taskin pause 001 -m "saving progress"'],
|
|
1491
2368
|
icon: "\u23F8\uFE0F"
|
|
1492
2369
|
},
|
|
1493
2370
|
{
|
|
1494
|
-
name:
|
|
1495
|
-
alias:
|
|
2371
|
+
name: colors2.highlight("taskin finish") + colors2.normal(" <task-id>"),
|
|
2372
|
+
alias: colors2.secondary("Alias: done"),
|
|
1496
2373
|
description: "Finish a task",
|
|
1497
2374
|
examples: ["taskin finish 001", "taskin done task-001"],
|
|
1498
2375
|
icon: "\u2705"
|
|
1499
2376
|
},
|
|
1500
2377
|
{
|
|
1501
|
-
name:
|
|
1502
|
-
alias:
|
|
2378
|
+
name: colors2.highlight("taskin lint") + colors2.normal(" [options]"),
|
|
2379
|
+
alias: colors2.secondary("Options: -p, --path <directory>"),
|
|
1503
2380
|
description: "Validate task markdown files",
|
|
1504
2381
|
examples: [
|
|
1505
2382
|
"taskin lint",
|
|
@@ -1510,43 +2387,43 @@ function showCustomHelp() {
|
|
|
1510
2387
|
}
|
|
1511
2388
|
];
|
|
1512
2389
|
commands.forEach((cmd, index) => {
|
|
1513
|
-
console.log(
|
|
1514
|
-
console.log(
|
|
1515
|
-
console.log(
|
|
2390
|
+
console.log(colors2.warning(`${cmd.icon} ${cmd.name}`));
|
|
2391
|
+
console.log(colors2.normal(` ${cmd.alias}`));
|
|
2392
|
+
console.log(colors2.info(` ${cmd.description}`));
|
|
1516
2393
|
console.log();
|
|
1517
|
-
console.log(
|
|
2394
|
+
console.log(colors2.normal(` ${colors2.info("\u{1F4DD} Examples:")}`));
|
|
1518
2395
|
cmd.examples.forEach((example) => {
|
|
1519
|
-
console.log(
|
|
2396
|
+
console.log(colors2.secondary(` ${example}`));
|
|
1520
2397
|
});
|
|
1521
2398
|
if (index < commands.length - 1) {
|
|
1522
2399
|
console.log();
|
|
1523
|
-
console.log(
|
|
2400
|
+
console.log(colors2.normal(" " + colors2.secondary("\u2500".repeat(50))));
|
|
1524
2401
|
console.log();
|
|
1525
2402
|
}
|
|
1526
2403
|
});
|
|
1527
2404
|
console.log();
|
|
1528
|
-
console.log(
|
|
2405
|
+
console.log(colors2.highlight("\u2550".repeat(60)));
|
|
1529
2406
|
console.log();
|
|
1530
|
-
console.log(
|
|
2407
|
+
console.log(colors2.info("\u{1F4A1} QUICK TIPS"));
|
|
1531
2408
|
console.log(
|
|
1532
|
-
|
|
1533
|
-
`${
|
|
2409
|
+
colors2.normal(
|
|
2410
|
+
`${colors2.warning("\u2022")} Use short IDs: ${colors2.highlight("001")}, ${colors2.highlight("task-001")}`
|
|
1534
2411
|
)
|
|
1535
2412
|
);
|
|
1536
2413
|
console.log(
|
|
1537
|
-
|
|
1538
|
-
`${
|
|
2414
|
+
colors2.normal(
|
|
2415
|
+
`${colors2.warning("\u2022")} All commands support ${colors2.highlight("--help")} for more options`
|
|
1539
2416
|
)
|
|
1540
2417
|
);
|
|
1541
2418
|
console.log(
|
|
1542
|
-
|
|
1543
|
-
`${
|
|
2419
|
+
colors2.normal(
|
|
2420
|
+
`${colors2.warning("\u2022")} Use aliases for faster commands: ${colors2.highlight("ls")}, ${colors2.highlight("begin")}, ${colors2.highlight("stop")}, ${colors2.highlight("done")}`
|
|
1544
2421
|
)
|
|
1545
2422
|
);
|
|
1546
2423
|
console.log();
|
|
1547
|
-
console.log(
|
|
2424
|
+
console.log(colors2.info("\u{1F527} FOR MORE HELP"));
|
|
1548
2425
|
console.log(
|
|
1549
|
-
|
|
2426
|
+
colors2.secondary("taskin ") + colors2.highlight("<command>") + colors2.secondary(" --help")
|
|
1550
2427
|
);
|
|
1551
2428
|
console.log();
|
|
1552
2429
|
return "";
|
|
@@ -1556,12 +2433,12 @@ function showCustomHelp() {
|
|
|
1556
2433
|
init_esm_shims();
|
|
1557
2434
|
import { readFileSync } from "fs";
|
|
1558
2435
|
import { dirname, join as join4 } from "path";
|
|
1559
|
-
import { fileURLToPath as
|
|
1560
|
-
var
|
|
1561
|
-
var
|
|
2436
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2437
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
2438
|
+
var __dirname3 = dirname(__filename3);
|
|
1562
2439
|
function getVersion() {
|
|
1563
2440
|
try {
|
|
1564
|
-
const packageJsonPath = join4(
|
|
2441
|
+
const packageJsonPath = join4(__dirname3, "../package.json");
|
|
1565
2442
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1566
2443
|
return packageJson.version;
|
|
1567
2444
|
} catch {
|
|
@@ -1571,7 +2448,7 @@ function getVersion() {
|
|
|
1571
2448
|
|
|
1572
2449
|
// src/main.ts
|
|
1573
2450
|
init_esm_shims();
|
|
1574
|
-
import { join as join5 } from "path";
|
|
2451
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1575
2452
|
|
|
1576
2453
|
// src/taskin.ts
|
|
1577
2454
|
init_esm_shims();
|
|
@@ -1590,8 +2467,8 @@ var Taskin = class {
|
|
|
1590
2467
|
status: taskFile.status,
|
|
1591
2468
|
title: taskFile.title,
|
|
1592
2469
|
type: taskFile.type,
|
|
1593
|
-
|
|
1594
|
-
//
|
|
2470
|
+
...taskFile.userId && { userId: taskFile.userId }
|
|
2471
|
+
// Keep userId if present
|
|
1595
2472
|
};
|
|
1596
2473
|
return task;
|
|
1597
2474
|
}).filter((task) => {
|
|
@@ -1601,7 +2478,7 @@ var Taskin = class {
|
|
|
1601
2478
|
if (options?.type && task.type !== options.type) {
|
|
1602
2479
|
return false;
|
|
1603
2480
|
}
|
|
1604
|
-
if (options?.assignee && task.
|
|
2481
|
+
if (options?.assignee && task.userId !== options.assignee) {
|
|
1605
2482
|
return false;
|
|
1606
2483
|
}
|
|
1607
2484
|
return true;
|
|
@@ -1662,7 +2539,12 @@ var Taskin = class {
|
|
|
1662
2539
|
// src/main.ts
|
|
1663
2540
|
function createTaskin(tasksDir) {
|
|
1664
2541
|
const resolvedTasksDir = tasksDir || join5(process.cwd(), "TASKS");
|
|
1665
|
-
const
|
|
2542
|
+
const taskinDir = join5(dirname2(resolvedTasksDir), ".taskin");
|
|
2543
|
+
const userRegistry = new UserRegistry({ taskinDir });
|
|
2544
|
+
const taskProvider = new FileSystemTaskProvider(
|
|
2545
|
+
resolvedTasksDir,
|
|
2546
|
+
userRegistry
|
|
2547
|
+
);
|
|
1666
2548
|
const taskManager = new TaskManager(taskProvider);
|
|
1667
2549
|
const linter = new FileSystemTaskLinter();
|
|
1668
2550
|
return new Taskin(taskProvider, taskManager, linter);
|
|
@@ -1683,6 +2565,8 @@ startCommand(program);
|
|
|
1683
2565
|
pauseCommand(program);
|
|
1684
2566
|
finishCommand(program);
|
|
1685
2567
|
lintCommand(program);
|
|
2568
|
+
dashboardCommand(program);
|
|
2569
|
+
mcpServerCommand(program);
|
|
1686
2570
|
if (process.argv.length <= 2) {
|
|
1687
2571
|
showCustomHelp();
|
|
1688
2572
|
process.exit(0);
|