taskin 1.0.4 → 1.0.6

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 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/finish.ts
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
- constructor(tasksDirectory) {
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 headerSection = content.split(/^##/m)[0];
56
- const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
57
- const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
58
- const userMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
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
- userId: userMatch ? userMatch[1].trim() : void 0,
65
- status: statusMatch ? statusMatch[1].trim().toLowerCase() : "pending",
66
- type: typeMatch ? typeMatch[1].trim().toLowerCase() : "feat",
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 updatedContent = currentContent.replace(/^Status:\s*.+$/im, `Status: ${task.status}`);
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 headerSection = content.split(/^##/m)[0];
88
- const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
89
- const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
90
- const userMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
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
- userId: userMatch ? userMatch[1].trim() : void 0,
97
- status: statusMatch ? statusMatch[1].trim().toLowerCase() : "pending",
98
- type: typeMatch ? typeMatch[1].trim().toLowerCase() : "feat",
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.mock.js
604
+ // ../task-manager/dist/task-manager.types.js
144
605
  init_esm_shims();
145
606
 
146
- // ../task-manager/dist/task-manager.types.js
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
- // src/commands/finish.ts
150
- import path5 from "path";
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
+ "&": "&amp;",
689
+ "<": "&lt;",
690
+ ">": "&gt;",
691
+ '"': "&quot;",
692
+ "'": "&#x27;",
693
+ "/": "&#x2F;"
694
+ };
695
+ return text.replace(/[&<>"'/]/g, (char) => htmlEscapeMap[char]);
696
+ }
151
697
 
152
- // src/lib/colors.ts
698
+ // ../utils/dist/ui.js
153
699
  init_esm_shims();
154
700
  import chalk from "chalk";
155
701
  var colors = {
156
- info: chalk.cyan,
702
+ primary: chalk.blue,
703
+ secondary: chalk.gray,
157
704
  success: chalk.green,
158
705
  warning: chalk.yellow,
159
706
  error: chalk.red,
160
- highlight: chalk.bold.white,
161
- secondary: chalk.gray,
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(colors.highlight("\u2550".repeat(60)));
176
- console.log(colors.highlight(`${icon} ${title}`));
177
- console.log(colors.highlight("\u2550".repeat(60)));
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(colors.success(`\u2713 ${message}`));
748
+ console.log(colors2.success(`\u2713 ${message}`));
182
749
  }
183
750
  function error(message) {
184
- console.error(colors.error(`\u2717 ${message}`));
751
+ console.error(colors2.error(`\u2717 ${message}`));
185
752
  }
186
753
  function info(message) {
187
- console.log(colors.info(`\u2139 ${message}`));
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 path3 from "path";
760
+ import path4 from "path";
194
761
  function isTaskinProject(cwd = process.cwd()) {
195
- return existsSync(path3.join(cwd, ".taskin.json"));
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 = path5.join(process.cwd(), "TASKS");
289
- const taskProvider = new FileSystemTaskProvider(tasksDir);
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
- colors.highlight(` ${commitType}(task-${normalizedId}): ${task.title}`)
1083
+ colors2.highlight(` ${commitType}(task-${normalizedId}): ${task.title}`)
315
1084
  );
316
1085
  console.log();
317
1086
  info("Next steps:");
318
- console.log(colors.secondary(" 1. Review your changes"));
319
- console.log(colors.secondary(" 2. Commit: git add . && git commit"));
320
- console.log(colors.secondary(" 3. Push: git push"));
321
- console.log(colors.secondary(" 4. Create a Pull Request"));
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: ${colors.highlight(providerId)}`);
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: ${colors.highlight(selectedProvider.name)}`);
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 ${colors.highlight(".taskin.json")}`);
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(colors.secondary(" 1. Run: taskin list"));
1403
+ console.log(colors2.secondary(" 1. Run: taskin list"));
635
1404
  console.log(
636
- colors.secondary(
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(colors.secondary(" 3. Start working: taskin start <task-id>"));
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 ${colors.highlight("TASKS/")} directory`);
1443
+ success(`\u2713 Created ${colors2.highlight("TASKS/")} directory`);
675
1444
  } else {
676
- info(`${colors.highlight("TASKS/")} directory already exists`);
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 ${colors.highlight("task-001-setup-project.md")}`
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 chalk2 from "chalk";
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 readFile(filePath, "utf-8");
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 readFile(join2(tasksDir, file), "utf-8");
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
- chalk2.green(`\u2705 All ${result.filesChecked} task files are valid!
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
- chalk2.bold(
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(chalk2.cyan(`
1684
+ console.log(chalk4.cyan(`
916
1685
  \u{1F4C4} ${file}`));
917
1686
  for (const error2 of fileErrors) {
918
- const icon = error2.severity === "error" ? chalk2.red("\u274C") : chalk2.yellow("\u26A0\uFE0F");
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
- chalk2.bold(
1694
+ chalk4.bold(
926
1695
  `
927
- \u{1F4CA} Summary: ${chalk2.red(result.errors.length + " error(s)")}, ${chalk2.yellow(result.warnings.length + " warning(s)")}
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 path6 from "path";
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 = path6.join(process.cwd(), "TASKS");
1043
- const taskProvider = new FileSystemTaskProvider(tasksDir);
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(colors.warning("No tasks found in TASKS/ directory"));
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(colors.warning("No tasks match the filters"));
1841
+ console.log(colors2.warning("No tasks match the filters"));
1069
1842
  return;
1070
1843
  }
1071
1844
  console.log(
1072
- colors.highlight(
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(colors.secondary("\u2500".repeat(100)));
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
- `${colors.info(task.id.padEnd(15))} ${statusColor(task.status.padEnd(15))} ${typeColor(task.type.padEnd(12))} ${colors.secondary((task.userId || "unknown").padEnd(15))} ${colors.normal(task.title)}`
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(colors.secondary("\u2500".repeat(100)));
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
- colors.info(
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 colors.secondary;
1875
+ return colors2.secondary;
1103
1876
  case "in-progress":
1104
- return colors.info;
1877
+ return colors2.info;
1105
1878
  case "done":
1106
- return colors.success;
1879
+ return colors2.success;
1107
1880
  case "blocked":
1108
- return colors.error;
1881
+ return colors2.error;
1109
1882
  default:
1110
- return colors.normal;
1883
+ return colors2.normal;
1111
1884
  }
1112
1885
  }
1113
1886
  function getTypeColor(type) {
1114
1887
  switch (type) {
1115
1888
  case "feat":
1116
- return colors.success;
1889
+ return colors2.success;
1117
1890
  case "fix":
1118
- return colors.error;
1891
+ return colors2.error;
1119
1892
  case "refactor":
1120
- return colors.warning;
1893
+ return colors2.warning;
1121
1894
  case "docs":
1122
- return colors.info;
1895
+ return colors2.info;
1123
1896
  case "test":
1124
- return colors.secondary;
1897
+ return colors2.secondary;
1125
1898
  case "chore":
1126
- return colors.normal;
1899
+ return colors2.normal;
1127
1900
  default:
1128
- return colors.normal;
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 path7 from "path";
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 = path7.join(process.cwd(), "TASKS");
2093
+ const tasksDir = path10.join(process.cwd(), "TASKS");
1229
2094
  if (!existsSync5(tasksDir)) {
1230
2095
  mkdirSync2(tasksDir, { recursive: true });
1231
2096
  }
1232
- const taskProvider = new FileSystemTaskProvider(tasksDir);
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 = path7.join(tasksDir, fileName);
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(colors.secondary(`\u{1F4C4} File: ${fileName}`));
1258
- console.log(colors.secondary(`\u{1F4C1} Path: ${filePath}`));
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(colors.info("Next steps:"));
1261
- console.log(colors.normal(` 1. Edit the task file to add more details`));
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
- colors.normal(
1264
- ` 2. Run ${colors.highlight("taskin start " + taskId)} to begin working on it`
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 path8 from "path";
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 = path8.join(process.cwd(), "TASKS");
1323
- const taskProvider = new FileSystemTaskProvider(tasksDir);
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(colors.highlight(` "${commitMessage}"`));
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(colors.secondary(` Message: "${commitMessage}"`));
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(colors.secondary(" 1. Switch to another task"));
2233
+ console.log(colors2.secondary(" 1. Switch to another task"));
1361
2234
  console.log(
1362
- colors.secondary(" 2. Or continue later with the same branch")
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 path9 from "path";
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 = path9.join(process.cwd(), "TASKS");
1401
- const taskProvider = new FileSystemTaskProvider(tasksDir);
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
- colors.secondary(
2302
+ colors2.secondary(
1426
2303
  " 1. Create a branch: git checkout -b feat/task-" + normalizedId
1427
2304
  )
1428
2305
  );
1429
- console.log(colors.secondary(" 2. Start coding! \u{1F4BB}"));
1430
- console.log(colors.secondary(' 3. Use "taskin pause" to save progress'));
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(colors.info("\u{1F4CB} AVAILABLE COMMANDS"));
1442
- console.log(colors.highlight("\u2550".repeat(60)));
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: colors.highlight("taskin init"),
1447
- alias: colors.secondary("Alias: setup"),
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: colors.highlight("taskin list") + colors.normal(" [filter]"),
1454
- alias: colors.secondary("Alias: ls"),
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: colors.highlight("taskin new"),
1466
- alias: colors.secondary("Alias: create"),
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: colors.highlight("taskin start") + colors.normal(" <task-id>"),
1477
- alias: colors.secondary("Alias: begin"),
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: colors.highlight("taskin pause") + colors.normal(" <task-id>"),
1488
- alias: colors.secondary("Alias: stop"),
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: colors.highlight("taskin finish") + colors.normal(" <task-id>"),
1495
- alias: colors.secondary("Alias: done"),
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: colors.highlight("taskin lint") + colors.normal(" [options]"),
1502
- alias: colors.secondary("Options: -p, --path <directory>"),
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(colors.warning(`${cmd.icon} ${cmd.name}`));
1514
- console.log(colors.normal(` ${cmd.alias}`));
1515
- console.log(colors.info(` ${cmd.description}`));
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(colors.normal(` ${colors.info("\u{1F4DD} Examples:")}`));
2394
+ console.log(colors2.normal(` ${colors2.info("\u{1F4DD} Examples:")}`));
1518
2395
  cmd.examples.forEach((example) => {
1519
- console.log(colors.secondary(` ${example}`));
2396
+ console.log(colors2.secondary(` ${example}`));
1520
2397
  });
1521
2398
  if (index < commands.length - 1) {
1522
2399
  console.log();
1523
- console.log(colors.normal(" " + colors.secondary("\u2500".repeat(50))));
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(colors.highlight("\u2550".repeat(60)));
2405
+ console.log(colors2.highlight("\u2550".repeat(60)));
1529
2406
  console.log();
1530
- console.log(colors.info("\u{1F4A1} QUICK TIPS"));
2407
+ console.log(colors2.info("\u{1F4A1} QUICK TIPS"));
1531
2408
  console.log(
1532
- colors.normal(
1533
- `${colors.warning("\u2022")} Use short IDs: ${colors.highlight("001")}, ${colors.highlight("task-001")}`
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
- colors.normal(
1538
- `${colors.warning("\u2022")} All commands support ${colors.highlight("--help")} for more options`
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
- colors.normal(
1543
- `${colors.warning("\u2022")} Use aliases for faster commands: ${colors.highlight("ls")}, ${colors.highlight("begin")}, ${colors.highlight("stop")}, ${colors.highlight("done")}`
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(colors.info("\u{1F527} FOR MORE HELP"));
2424
+ console.log(colors2.info("\u{1F527} FOR MORE HELP"));
1548
2425
  console.log(
1549
- colors.secondary("taskin ") + colors.highlight("<command>") + colors.secondary(" --help")
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 fileURLToPath2 } from "url";
1560
- var __filename2 = fileURLToPath2(import.meta.url);
1561
- var __dirname2 = dirname(__filename2);
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(__dirname2, "../package.json");
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
- assignee: taskFile.userId
1594
- // Map userId to assignee
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.assignee !== options.assignee) {
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 taskProvider = new FileSystemTaskProvider(resolvedTasksDir);
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);