taskin 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -98,6 +98,7 @@ Taskin is built as a modular ecosystem. Besides the CLI, you can use individual
98
98
 
99
99
  - `taskin init` - Initialize Taskin in your project with interactive setup
100
100
  - `taskin list` - List all tasks
101
+ - `taskin new` - Create a new task (alias: `create`)
101
102
  - `taskin start <id>` - Start working on a task
102
103
  - `taskin pause <id>` - Pause work on a task
103
104
  - `taskin finish <id>` - Complete a task
@@ -154,7 +155,55 @@ Taskin uses a **plugin-based architecture** with dynamic provider loading:
154
155
 
155
156
  📚 See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed technical documentation.
156
157
 
157
- ## 📖 Examples
158
+ ## 📝 Creating Tasks
159
+
160
+ Use the `taskin new` command to create new task files:
161
+
162
+ ### Interactive Mode (Recommended)
163
+
164
+ Simply run without arguments for a guided experience:
165
+
166
+ ```bash
167
+ taskin new
168
+ ```
169
+
170
+ You'll be prompted to select:
171
+
172
+ - Task type (feat, fix, refactor, docs, test, chore)
173
+ - Title
174
+ - Description (optional)
175
+ - Assignee (optional)
176
+
177
+ ### Command-line Mode
178
+
179
+ Or provide all options directly:
180
+
181
+ ```bash
182
+ # Create a new feature task
183
+ taskin new -t feat -T "Add user authentication" -d "Implement JWT-based auth" -u "John Doe"
184
+
185
+ # Create a bug fix task
186
+ taskin new --type fix --title "Fix login error" --user "Developer"
187
+
188
+ # Using the 'create' alias
189
+ taskin create -t docs -T "Update README"
190
+ ```
191
+
192
+ **Options:**
193
+
194
+ - `-t, --type <type>` - Task type: feat, fix, refactor, docs, test, chore
195
+ - `-T, --title <title>` - Task title (required in command-line mode)
196
+ - `-d, --description <description>` - Task description
197
+ - `-u, --user <user>` - Assigned user
198
+
199
+ The command will:
200
+
201
+ 1. Auto-generate a task number (e.g., 05)
202
+ 2. Create a markdown file in `TASKS/` directory
203
+ 3. Use a slug from the title for the filename
204
+ 4. Pre-populate with a standard template
205
+
206
+ ## �📖 Examples
158
207
 
159
208
  See [EXAMPLES.md](./EXAMPLES.md) for detailed usage examples and workflows.
160
209
 
package/dist/index.js CHANGED
@@ -13,9 +13,13 @@ var __esm = (fn, res) => function __init() {
13
13
  // ../../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
14
  import path from "path";
15
15
  import { fileURLToPath } from "url";
16
+ var getFilename, getDirname, __dirname;
16
17
  var init_esm_shims = __esm({
17
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"() {
18
19
  "use strict";
20
+ getFilename = () => fileURLToPath(import.meta.url);
21
+ getDirname = () => path.dirname(getFilename());
22
+ __dirname = /* @__PURE__ */ getDirname();
19
23
  }
20
24
  });
21
25
 
@@ -26,54 +30,52 @@ import { Command } from "commander";
26
30
  // src/commands/finish.ts
27
31
  init_esm_shims();
28
32
 
29
- // ../fs-task-provider/src/index.ts
33
+ // ../fs-task-provider/dist/index.js
30
34
  init_esm_shims();
31
35
 
32
- // ../fs-task-provider/src/fs-task-provider.ts
36
+ // ../fs-task-provider/dist/fs-task-provider.js
33
37
  init_esm_shims();
34
38
  import { promises as fs } from "fs";
35
39
  import path2 from "path";
36
40
  var FileSystemTaskProvider = class {
41
+ tasksDirectory;
37
42
  constructor(tasksDirectory) {
38
43
  this.tasksDirectory = tasksDirectory;
39
44
  }
40
45
  async findTask(taskId) {
41
46
  const files = await fs.readdir(this.tasksDirectory);
42
- const taskFile = files.find(
43
- (file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md")
44
- );
47
+ const taskFile = files.find((file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md"));
45
48
  if (!taskFile) {
46
49
  return void 0;
47
50
  }
48
51
  const filePath = path2.join(this.tasksDirectory, taskFile);
49
52
  const content = await fs.readFile(filePath, "utf-8");
50
- const titleMatch = content.match(/^# Task \d+ - (.+)$/m);
51
- const title = titleMatch ? titleMatch[1] : "Untitled";
53
+ const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
54
+ 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);
52
59
  const task = {
53
60
  id: taskId,
54
61
  title,
55
62
  content,
56
63
  filePath,
57
- userId: "unknown",
58
- status: "pending",
59
- type: "feat",
64
+ userId: userMatch ? userMatch[1].trim() : void 0,
65
+ status: statusMatch ? statusMatch[1].trim().toLowerCase() : "pending",
66
+ type: typeMatch ? typeMatch[1].trim().toLowerCase() : "feat",
60
67
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
61
68
  };
62
69
  return task;
63
70
  }
64
71
  async updateTask(task) {
65
72
  const currentContent = await fs.readFile(task.filePath, "utf-8");
66
- const updatedContent = currentContent.replace(
67
- /^Status:\s*.+$/im,
68
- `Status: ${task.status}`
69
- );
73
+ const updatedContent = currentContent.replace(/^Status:\s*.+$/im, `Status: ${task.status}`);
70
74
  await fs.writeFile(task.filePath, updatedContent, "utf-8");
71
75
  }
72
76
  async getAllTasks() {
73
77
  const files = await fs.readdir(this.tasksDirectory);
74
- const taskFiles = files.filter(
75
- (file) => file.startsWith("task-") && file.endsWith(".md")
76
- );
78
+ const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md"));
77
79
  const tasks = [];
78
80
  for (const file of taskFiles) {
79
81
  const filePath = path2.join(this.tasksDirectory, file);
@@ -102,12 +104,13 @@ var FileSystemTaskProvider = class {
102
104
  }
103
105
  };
104
106
 
105
- // ../task-manager/src/index.ts
107
+ // ../task-manager/dist/index.js
106
108
  init_esm_shims();
107
109
 
108
- // ../task-manager/src/task-manager.ts
110
+ // ../task-manager/dist/task-manager.js
109
111
  init_esm_shims();
110
112
  var TaskManager = class {
113
+ taskProvider;
111
114
  constructor(taskProvider) {
112
115
  this.taskProvider = taskProvider;
113
116
  }
@@ -137,14 +140,14 @@ var TaskManager = class {
137
140
  }
138
141
  };
139
142
 
140
- // ../task-manager/src/task-manager.mock.ts
143
+ // ../task-manager/dist/task-manager.mock.js
141
144
  init_esm_shims();
142
145
 
143
- // ../task-manager/src/task-manager.types.ts
146
+ // ../task-manager/dist/task-manager.types.js
144
147
  init_esm_shims();
145
148
 
146
149
  // src/commands/finish.ts
147
- import path3 from "path";
150
+ import path5 from "path";
148
151
 
149
152
  // src/lib/colors.ts
150
153
  init_esm_shims();
@@ -184,6 +187,51 @@ function info(message) {
184
187
  console.log(colors.info(`\u2139 ${message}`));
185
188
  }
186
189
 
190
+ // src/lib/project-check.ts
191
+ init_esm_shims();
192
+ import { existsSync } from "fs";
193
+ import path3 from "path";
194
+ function isTaskinProject(cwd = process.cwd()) {
195
+ return existsSync(path3.join(cwd, ".taskin.json"));
196
+ }
197
+ function requireTaskinProject(cwd = process.cwd()) {
198
+ if (!isTaskinProject(cwd)) {
199
+ error(
200
+ 'This directory is not initialized as a taskin project. Run "taskin init" first.'
201
+ );
202
+ process.exit(1);
203
+ }
204
+ }
205
+
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
+
187
235
  // src/commands/define-command/index.ts
188
236
  init_esm_shims();
189
237
 
@@ -223,6 +271,10 @@ var finishCommand = defineCommand({
223
271
  {
224
272
  flags: "-s, --skip-update",
225
273
  description: "Skip updating task status"
274
+ },
275
+ {
276
+ flags: "--no-sound",
277
+ description: "Disable finish sound"
226
278
  }
227
279
  ],
228
280
  handler: async (taskId, options) => {
@@ -230,9 +282,10 @@ var finishCommand = defineCommand({
230
282
  }
231
283
  });
232
284
  async function finishTask(taskId, options) {
285
+ requireTaskinProject();
233
286
  printHeader(`Finishing Task ${taskId}`, "\u2705");
234
287
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
235
- const tasksDir = path3.join(process.cwd(), "TASKS");
288
+ const tasksDir = path5.join(process.cwd(), "TASKS");
236
289
  const taskProvider = new FileSystemTaskProvider(tasksDir);
237
290
  const taskManager = new TaskManager(taskProvider);
238
291
  const task = await taskProvider.findTask(normalizedId);
@@ -269,11 +322,14 @@ async function finishTask(taskId, options) {
269
322
  console.log();
270
323
  success("Great work! \u{1F680}");
271
324
  console.log();
325
+ if (options.sound !== false) {
326
+ playSound("finish");
327
+ }
272
328
  }
273
329
 
274
330
  // src/commands/init.ts
275
331
  init_esm_shims();
276
- import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
332
+ import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
277
333
  import inquirer from "inquirer";
278
334
  import { join } from "path";
279
335
 
@@ -283,12 +339,12 @@ init_esm_shims();
283
339
  // src/lib/provider-installer/provider-installer.ts
284
340
  init_esm_shims();
285
341
  import { execSync } from "child_process";
286
- import { existsSync } from "fs";
342
+ import { existsSync as existsSync3 } from "fs";
287
343
  function detectPackageManager() {
288
- if (existsSync("pnpm-lock.yaml")) {
344
+ if (existsSync3("pnpm-lock.yaml")) {
289
345
  return "pnpm";
290
346
  }
291
- if (existsSync("yarn.lock")) {
347
+ if (existsSync3("yarn.lock")) {
292
348
  return "yarn";
293
349
  }
294
350
  return "npm";
@@ -483,6 +539,10 @@ var initCommand = defineCommand({
483
539
  {
484
540
  flags: "-f, --force",
485
541
  description: "Force initialization (overwrite existing configuration)"
542
+ },
543
+ {
544
+ flags: "-p, --provider <provider>",
545
+ description: "Provider to use (fs, redmine, jira, github) - skips interactive prompt"
486
546
  }
487
547
  ],
488
548
  handler: async (options) => {
@@ -493,7 +553,7 @@ async function initializeTaskin(options) {
493
553
  printHeader("Initializing Taskin", "\u{1F3AF}");
494
554
  const cwd = process.cwd();
495
555
  const configFile = join(cwd, ".taskin.json");
496
- if (existsSync2(configFile) && !options.force) {
556
+ if (existsSync4(configFile) && !options.force) {
497
557
  error("Taskin is already initialized in this project");
498
558
  info("Use --force to reinitialize");
499
559
  process.exit(1);
@@ -509,15 +569,25 @@ async function initializeTaskin(options) {
509
569
  disabled: p.status === "coming-soon" ? "Not yet implemented" : false
510
570
  };
511
571
  });
512
- const { providerId } = await inquirer.prompt([
513
- {
514
- type: "list",
515
- name: "providerId",
516
- message: "Select a task provider:",
517
- choices,
518
- default: "fs"
519
- }
520
- ]);
572
+ let providerId;
573
+ if (options.provider) {
574
+ providerId = options.provider;
575
+ info(`Using provider from command line: ${colors.highlight(providerId)}`);
576
+ } else if (process.env.CI === "true") {
577
+ providerId = "fs";
578
+ info("CI environment detected, using default provider: fs");
579
+ } else {
580
+ const response = await inquirer.prompt([
581
+ {
582
+ type: "list",
583
+ name: "providerId",
584
+ message: "Select a task provider:",
585
+ choices,
586
+ default: "fs"
587
+ }
588
+ ]);
589
+ providerId = response.providerId;
590
+ }
521
591
  const selectedProvider = getProviderById(providerId);
522
592
  if (!selectedProvider) {
523
593
  error(`Provider "${providerId}" not found`);
@@ -542,7 +612,7 @@ async function initializeTaskin(options) {
542
612
  writeFileSync(configFile, JSON.stringify(config, null, 2), "utf-8");
543
613
  success(`\u2713 Created ${colors.highlight(".taskin.json")}`);
544
614
  const gitignorePath = join(cwd, ".gitignore");
545
- if (existsSync2(gitignorePath)) {
615
+ if (existsSync4(gitignorePath)) {
546
616
  const gitignoreContent = __require("fs").readFileSync(gitignorePath, "utf-8");
547
617
  if (!gitignoreContent.includes(".taskin.json")) {
548
618
  info("Adding .taskin.json to .gitignore...");
@@ -564,7 +634,7 @@ async function initializeTaskin(options) {
564
634
  console.log(colors.secondary(" 1. Run: taskin list"));
565
635
  console.log(
566
636
  colors.secondary(
567
- selectedProvider.id === "fs" ? " 2. Edit or create tasks in TASKS/" : ` 2. Tasks will be synced with ${selectedProvider.name}`
637
+ selectedProvider.id === "fs" ? " 2. Create a new task: taskin new (interactive mode)" : ` 2. Tasks will be synced with ${selectedProvider.name}`
568
638
  )
569
639
  );
570
640
  console.log(colors.secondary(" 3. Start working: taskin start <task-id>"));
@@ -598,15 +668,16 @@ async function setupProviderConfig(provider, cwd) {
598
668
  }
599
669
  async function setupFileSystemProvider(cwd) {
600
670
  const tasksDir = join(cwd, "TASKS");
601
- if (!existsSync2(tasksDir)) {
671
+ if (!existsSync4(tasksDir)) {
602
672
  info(`Creating TASKS directory...`);
603
673
  mkdirSync(tasksDir, { recursive: true });
604
674
  success(`\u2713 Created ${colors.highlight("TASKS/")} directory`);
605
675
  } else {
606
676
  info(`${colors.highlight("TASKS/")} directory already exists`);
677
+ success("\u2713 Directory is ready to use");
607
678
  }
608
679
  const sampleTaskFile = join(tasksDir, "task-001-setup-project.md");
609
- if (!existsSync2(sampleTaskFile)) {
680
+ if (!existsSync4(sampleTaskFile)) {
610
681
  info("Creating sample task...");
611
682
  const sampleTask = `# Task 001 \u2014 Setup Project
612
683
 
@@ -942,7 +1013,7 @@ async function executeLint(options) {
942
1013
 
943
1014
  // src/commands/list.ts
944
1015
  init_esm_shims();
945
- import path4 from "path";
1016
+ import path6 from "path";
946
1017
  var listCommand = defineCommand({
947
1018
  name: "list [filter]",
948
1019
  description: "\u{1F4CA} List all tasks in the project",
@@ -966,8 +1037,9 @@ var listCommand = defineCommand({
966
1037
  }
967
1038
  });
968
1039
  async function listTasks(filter, options) {
1040
+ requireTaskinProject();
969
1041
  printHeader("Task List", "\u{1F4CA}");
970
- const tasksDir = path4.join(process.cwd(), "TASKS");
1042
+ const tasksDir = path6.join(process.cwd(), "TASKS");
971
1043
  const taskProvider = new FileSystemTaskProvider(tasksDir);
972
1044
  const tasks = await taskProvider.getAllTasks();
973
1045
  if (tasks.length === 0) {
@@ -1057,9 +1129,170 @@ function getTypeColor(type) {
1057
1129
  }
1058
1130
  }
1059
1131
 
1132
+ // src/commands/new.ts
1133
+ init_esm_shims();
1134
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1135
+ import inquirer2 from "inquirer";
1136
+ import path7 from "path";
1137
+ var createCommand = defineCommand({
1138
+ name: "new",
1139
+ description: "\u2795 Create a new task",
1140
+ alias: "create",
1141
+ options: [
1142
+ {
1143
+ flags: "-t, --type <type>",
1144
+ description: "Task type (feat, fix, refactor, docs, test, chore)"
1145
+ },
1146
+ {
1147
+ flags: "-T, --title <title>",
1148
+ description: "Task title"
1149
+ },
1150
+ {
1151
+ flags: "-d, --description <description>",
1152
+ description: "Task description"
1153
+ },
1154
+ {
1155
+ flags: "-u, --user <user>",
1156
+ description: "Assignee user"
1157
+ }
1158
+ ],
1159
+ handler: async (options) => {
1160
+ await createTask(options);
1161
+ }
1162
+ });
1163
+ async function createTask(options) {
1164
+ requireTaskinProject();
1165
+ printHeader("Create New Task", "\u2795");
1166
+ const validTypes = ["feat", "fix", "refactor", "docs", "test", "chore"];
1167
+ if (!options.type && !options.title) {
1168
+ info("Interactive mode - Answer the questions below:");
1169
+ console.log();
1170
+ const answers = await inquirer2.prompt([
1171
+ {
1172
+ type: "list",
1173
+ name: "type",
1174
+ message: "Select task type:",
1175
+ choices: [
1176
+ { name: "\u2728 feat - New feature", value: "feat" },
1177
+ { name: "\u{1F41B} fix - Bug fix", value: "fix" },
1178
+ { name: "\u267B\uFE0F refactor - Code refactoring", value: "refactor" },
1179
+ { name: "\u{1F4DD} docs - Documentation", value: "docs" },
1180
+ { name: "\u2705 test - Tests", value: "test" },
1181
+ { name: "\u{1F527} chore - Maintenance", value: "chore" }
1182
+ ],
1183
+ default: "feat"
1184
+ },
1185
+ {
1186
+ type: "input",
1187
+ name: "title",
1188
+ message: "Task title:",
1189
+ validate: (input) => {
1190
+ if (input.trim().length === 0) {
1191
+ return "Title is required";
1192
+ }
1193
+ return true;
1194
+ }
1195
+ },
1196
+ {
1197
+ type: "input",
1198
+ name: "description",
1199
+ message: "Task description (optional):"
1200
+ },
1201
+ {
1202
+ type: "input",
1203
+ name: "user",
1204
+ message: "Assignee (optional):",
1205
+ default: "A definir"
1206
+ }
1207
+ ]);
1208
+ options.type = answers.type;
1209
+ options.title = answers.title;
1210
+ options.description = answers.description;
1211
+ options.user = answers.user;
1212
+ console.log();
1213
+ }
1214
+ if (!options.type) {
1215
+ error("Task type is required. Use --type <type>");
1216
+ return;
1217
+ }
1218
+ if (!options.title) {
1219
+ error("Task title is required. Use --title <title>");
1220
+ return;
1221
+ }
1222
+ if (!validTypes.includes(options.type)) {
1223
+ error(
1224
+ `Invalid task type: ${options.type}. Must be one of: ${validTypes.join(", ")}`
1225
+ );
1226
+ return;
1227
+ }
1228
+ const tasksDir = path7.join(process.cwd(), "TASKS");
1229
+ if (!existsSync5(tasksDir)) {
1230
+ mkdirSync2(tasksDir, { recursive: true });
1231
+ }
1232
+ const taskProvider = new FileSystemTaskProvider(tasksDir);
1233
+ const allTasks = await taskProvider.getAllTasks();
1234
+ const taskNumbers = allTasks.map((task) => {
1235
+ const match = task.id.match(/^(\d+)$/);
1236
+ return match ? parseInt(match[1], 10) : 0;
1237
+ }).filter((num) => !isNaN(num));
1238
+ const nextNumber = taskNumbers.length > 0 ? Math.max(...taskNumbers) + 1 : 1;
1239
+ const taskId = String(nextNumber).padStart(3, "0");
1240
+ const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1241
+ const fileName = `task-${taskId}-${titleSlug}.md`;
1242
+ const filePath = path7.join(tasksDir, fileName);
1243
+ if (existsSync5(filePath)) {
1244
+ error(`Task file already exists: ${fileName}`);
1245
+ return;
1246
+ }
1247
+ const taskContent = generateTaskMarkdown({
1248
+ id: taskId,
1249
+ type: options.type,
1250
+ title: options.title,
1251
+ description: options.description || "",
1252
+ user: options.user || "A definir"
1253
+ });
1254
+ writeFileSync2(filePath, taskContent, "utf-8");
1255
+ console.log();
1256
+ success(`Task ${taskId} created successfully!`);
1257
+ console.log(colors.secondary(`\u{1F4C4} File: ${fileName}`));
1258
+ console.log(colors.secondary(`\u{1F4C1} Path: ${filePath}`));
1259
+ console.log();
1260
+ console.log(colors.info("Next steps:"));
1261
+ console.log(colors.normal(` 1. Edit the task file to add more details`));
1262
+ console.log(
1263
+ colors.normal(
1264
+ ` 2. Run ${colors.highlight("taskin start " + taskId)} to begin working on it`
1265
+ )
1266
+ );
1267
+ console.log();
1268
+ }
1269
+ function generateTaskMarkdown(data) {
1270
+ return `# Task ${data.id} \u2014 ${data.title}
1271
+
1272
+ Status: pending
1273
+ Type: ${data.type}
1274
+ Assignee: ${data.user}
1275
+
1276
+ ## Description
1277
+
1278
+ ${data.description || "Add task description here..."}
1279
+
1280
+ ## Tasks
1281
+
1282
+ - [ ] Task 1
1283
+ - [ ] Task 2
1284
+ - [ ] Task 3
1285
+
1286
+ ## Notes
1287
+
1288
+ Add any relevant notes or links here.
1289
+ `;
1290
+ }
1291
+
1060
1292
  // src/commands/pause.ts
1061
1293
  init_esm_shims();
1062
- import path5 from "path";
1294
+ import { execSync as execSync2 } from "child_process";
1295
+ import path8 from "path";
1063
1296
  var pauseCommand = defineCommand({
1064
1297
  name: "pause <task-id>",
1065
1298
  description: "\u23F8\uFE0F Pause work on a task",
@@ -1072,6 +1305,10 @@ var pauseCommand = defineCommand({
1072
1305
  {
1073
1306
  flags: "-s, --skip-commit",
1074
1307
  description: "Skip commit (just show what would be done)"
1308
+ },
1309
+ {
1310
+ flags: "--no-sound",
1311
+ description: "Disable pause sound"
1075
1312
  }
1076
1313
  ],
1077
1314
  handler: async (taskId, options) => {
@@ -1079,9 +1316,10 @@ var pauseCommand = defineCommand({
1079
1316
  }
1080
1317
  });
1081
1318
  async function pauseTask(taskId, options) {
1319
+ requireTaskinProject();
1082
1320
  printHeader(`Pausing Task ${taskId}`, "\u23F8\uFE0F");
1083
1321
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
1084
- const tasksDir = path5.join(process.cwd(), "TASKS");
1322
+ const tasksDir = path8.join(process.cwd(), "TASKS");
1085
1323
  const taskProvider = new FileSystemTaskProvider(tasksDir);
1086
1324
  const task = await taskProvider.findTask(normalizedId);
1087
1325
  if (!task) {
@@ -1104,21 +1342,35 @@ async function pauseTask(taskId, options) {
1104
1342
  info("Creating commit...");
1105
1343
  console.log(colors.secondary(` Message: "${commitMessage}"`));
1106
1344
  console.log();
1345
+ try {
1346
+ execSync2("git add -A", { cwd: process.cwd(), stdio: "ignore" });
1347
+ execSync2(`git commit -m "${commitMessage}"`, {
1348
+ cwd: process.cwd(),
1349
+ stdio: "ignore"
1350
+ });
1351
+ } catch {
1352
+ }
1353
+ const updatedTask = { ...task, status: "pending" };
1354
+ await taskProvider.updateTask(updatedTask);
1107
1355
  success("Task paused successfully!");
1108
1356
  info("Commit created (not pushed)");
1357
+ info("Status updated to pending");
1109
1358
  console.log();
1110
1359
  info("Next steps:");
1111
1360
  console.log(colors.secondary(" 1. Switch to another task"));
1112
1361
  console.log(
1113
1362
  colors.secondary(" 2. Or continue later with the same branch")
1114
1363
  );
1364
+ if (options.sound !== false) {
1365
+ playSound("stop");
1366
+ }
1115
1367
  }
1116
1368
  console.log();
1117
1369
  }
1118
1370
 
1119
1371
  // src/commands/start.ts
1120
1372
  init_esm_shims();
1121
- import path6 from "path";
1373
+ import path9 from "path";
1122
1374
  var startCommand = defineCommand({
1123
1375
  name: "start <task-id>",
1124
1376
  description: "\u{1F680} Start working on a task",
@@ -1131,6 +1383,10 @@ var startCommand = defineCommand({
1131
1383
  {
1132
1384
  flags: "-b, --base <branch>",
1133
1385
  description: "Base branch to create from (default: current)"
1386
+ },
1387
+ {
1388
+ flags: "--no-sound",
1389
+ description: "Disable start sound"
1134
1390
  }
1135
1391
  ],
1136
1392
  handler: async (taskId, options) => {
@@ -1138,9 +1394,10 @@ var startCommand = defineCommand({
1138
1394
  }
1139
1395
  });
1140
1396
  async function startTask(taskId, _options) {
1397
+ requireTaskinProject();
1141
1398
  printHeader(`Starting Task ${taskId}`, "\u{1F680}");
1142
1399
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
1143
- const tasksDir = path6.join(process.cwd(), "TASKS");
1400
+ const tasksDir = path9.join(process.cwd(), "TASKS");
1144
1401
  const taskProvider = new FileSystemTaskProvider(tasksDir);
1145
1402
  const taskManager = new TaskManager(taskProvider);
1146
1403
  const task = await taskProvider.findTask(normalizedId);
@@ -1172,6 +1429,9 @@ async function startTask(taskId, _options) {
1172
1429
  console.log(colors.secondary(" 2. Start coding! \u{1F4BB}"));
1173
1430
  console.log(colors.secondary(' 3. Use "taskin pause" to save progress'));
1174
1431
  console.log();
1432
+ if (_options.sound !== false) {
1433
+ playSound("start");
1434
+ }
1175
1435
  }
1176
1436
 
1177
1437
  // src/lib/help.ts
@@ -1201,6 +1461,17 @@ function showCustomHelp() {
1201
1461
  ],
1202
1462
  icon: "\u{1F4CA}"
1203
1463
  },
1464
+ {
1465
+ name: colors.highlight("taskin new"),
1466
+ alias: colors.secondary("Alias: create"),
1467
+ description: "Create a new task",
1468
+ examples: [
1469
+ 'taskin new -t feat -T "Add login" -d "Implement user authentication"',
1470
+ 'taskin new --type fix --title "Fix bug" --user "John"',
1471
+ 'taskin create -t docs -T "Update README"'
1472
+ ],
1473
+ icon: "\u{1F4DD}"
1474
+ },
1204
1475
  {
1205
1476
  name: colors.highlight("taskin start") + colors.normal(" <task-id>"),
1206
1477
  alias: colors.secondary("Alias: begin"),
@@ -1407,6 +1678,7 @@ program.name("taskin").description("\u{1F680} Task Management System").version(g
1407
1678
  });
1408
1679
  initCommand(program);
1409
1680
  listCommand(program);
1681
+ createCommand(program);
1410
1682
  startCommand(program);
1411
1683
  pauseCommand(program);
1412
1684
  finishCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskin",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Task management system integrated with Git workflows",
5
5
  "motivation": "Provide a CLI tool for task management integrated with Git workflows",
6
6
  "solve": "Simplifies task tracking and management directly from the command line",
@@ -57,6 +57,7 @@
57
57
  },
58
58
  "files": [
59
59
  "dist",
60
+ "sounds",
60
61
  "README.md",
61
62
  "LICENSE"
62
63
  ],
@@ -76,9 +77,11 @@
76
77
  },
77
78
  "dependencies": {
78
79
  "@types/inquirer": "^9.0.9",
80
+ "@types/play-sound": "^1.1.2",
79
81
  "chalk": "^5.6.2",
80
82
  "commander": "^12.1.0",
81
83
  "inquirer": "^12.11.0",
84
+ "play-sound": "^1.1.6",
82
85
  "zod": "^3.25.76"
83
86
  },
84
87
  "devDependencies": {
@@ -0,0 +1,82 @@
1
+ # Audio Sounds for Taskin
2
+
3
+ This directory contains optional audio files that can be played during CLI operations.
4
+
5
+ ## Included Sounds
6
+
7
+ ### `start.mp3`
8
+
9
+ Played when you start a task with `taskin start` command.
10
+ Sound effect: CS:GO "Go Go Go!"
11
+
12
+ ### `finish.mp3`
13
+
14
+ Played when you finish a task with `taskin finish` or `taskin done` command.
15
+ Sound effect: CS:GO "Bomb has been planted"
16
+
17
+ ### `stop.mp3` (TODO)
18
+
19
+ Played when you pause a task with `taskin pause` or `taskin stop` command.
20
+ **Note:** This file needs to be added. Sound effect: (to be defined)
21
+
22
+ ## How to Add Custom Sounds
23
+
24
+ 1. **Built-in sounds**: Place MP3 files in this directory (`packages/cli/sounds/`)
25
+ 2. **Project-specific sounds**: Create a `.taskin/` directory in your project root and add MP3 files there
26
+
27
+ ## Adding Your Own Sounds
28
+
29
+ To add custom sounds:
30
+
31
+ 1. Create a `.taskin/` directory in your project:
32
+
33
+ ```bash
34
+ mkdir .taskin
35
+ ```
36
+
37
+ 2. Add your MP3 files (you can customize any of these):
38
+
39
+ ```bash
40
+ cp /path/to/your/start-sound.mp3 .taskin/start.mp3
41
+ cp /path/to/your/finish-sound.mp3 .taskin/finish.mp3
42
+ cp /path/to/your/pause-sound.mp3 .taskin/stop.mp3
43
+ ```
44
+
45
+ 3. The sounds will play automatically on the respective commands!
46
+
47
+ ## Disabling Sound
48
+
49
+ To disable sounds, use the `--no-sound` flag:
50
+
51
+ ```bash
52
+ taskin start 001 --no-sound
53
+ taskin finish 001 --no-sound
54
+ taskin pause 001 --no-sound
55
+ ```
56
+
57
+ ## Requirements
58
+
59
+ The sound player uses system audio utilities. Make sure you have one of these installed:
60
+
61
+ - **Linux**: `mpg123`, `ffplay`, or `mplayer`
62
+ - **macOS**: `afplay` (built-in)
63
+ - **Windows**: Built-in Windows Media Player
64
+
65
+ ### Installing audio players on Linux:
66
+
67
+ ```bash
68
+ # Ubuntu/Debian
69
+ sudo apt-get install mpg123
70
+
71
+ # Fedora
72
+ sudo dnf install mpg123
73
+
74
+ # Arch
75
+ sudo pacman -S mpg123
76
+ ```
77
+
78
+ ## Notes
79
+
80
+ - Sounds are played asynchronously and won't block CLI operations
81
+ - If no sound file is found, the command executes silently
82
+ - Sound playback errors are silently ignored
Binary file
Binary file
package/dist/index.d.ts DELETED
@@ -1,383 +0,0 @@
1
- #!/usr/bin/env node
2
- import { z } from 'zod';
3
-
4
- /**
5
- * Task identifier schema.
6
- * Represents a unique identifier for a task (UUID format).
7
- *
8
- * @public
9
- */
10
- declare const TaskIdSchema: z.ZodBranded<z.ZodString, "TaskId">;
11
- /**
12
- * Runtime validator for task type values.
13
- * Use this to validate user input or external data.
14
- *
15
- * @public
16
- */
17
- declare const TaskTypeSchema: z.ZodEnum<["feat", "fix", "refactor", "docs", "test", "chore"]>;
18
- /**
19
- * Runtime validator for task objects.
20
- * Use this to parse and validate task data from external sources (API, database, forms).
21
- *
22
- * @public
23
- * @example
24
- * ```ts
25
- * // Parse and validate task data
26
- * const task = TaskSchema.parse(userInput);
27
- *
28
- * // Safe parse with error handling
29
- * const result = TaskSchema.safeParse(untrustedData);
30
- * if (result.success) {
31
- * console.log(result.data);
32
- * }
33
- * ```
34
- */
35
- declare const TaskSchema: z.ZodObject<{
36
- createdAt: z.ZodString;
37
- id: z.ZodBranded<z.ZodString, "TaskId">;
38
- status: z.ZodEnum<["pending", "in-progress", "done", "blocked"]>;
39
- title: z.ZodString;
40
- type: z.ZodEnum<["feat", "fix", "refactor", "docs", "test", "chore"]>;
41
- assignee: z.ZodOptional<z.ZodString>;
42
- completed: z.ZodOptional<z.ZodBoolean>;
43
- description: z.ZodOptional<z.ZodString>;
44
- userId: z.ZodOptional<z.ZodString>;
45
- }, "strip", z.ZodTypeAny, {
46
- createdAt: string;
47
- id: string & z.BRAND<"TaskId">;
48
- status: "pending" | "in-progress" | "done" | "blocked";
49
- title: string;
50
- type: "feat" | "fix" | "refactor" | "docs" | "test" | "chore";
51
- assignee?: string | undefined;
52
- completed?: boolean | undefined;
53
- description?: string | undefined;
54
- userId?: string | undefined;
55
- }, {
56
- createdAt: string;
57
- id: string;
58
- status: "pending" | "in-progress" | "done" | "blocked";
59
- title: string;
60
- type: "feat" | "fix" | "refactor" | "docs" | "test" | "chore";
61
- assignee?: string | undefined;
62
- completed?: boolean | undefined;
63
- description?: string | undefined;
64
- userId?: string | undefined;
65
- }>;
66
- /**
67
- * Runtime validator for user objects.
68
- * Use this to parse and validate user data from external sources.
69
- *
70
- * @public
71
- */
72
- declare const UserSchema: z.ZodObject<{
73
- email: z.ZodString;
74
- id: z.ZodString;
75
- name: z.ZodString;
76
- }, "strip", z.ZodTypeAny, {
77
- id: string;
78
- email: string;
79
- name: string;
80
- }, {
81
- id: string;
82
- email: string;
83
- name: string;
84
- }>;
85
-
86
- /**
87
- * Taskin Core Interface
88
- * Main interface for task management operations
89
- */
90
-
91
- interface ListTasksOptions {
92
- assignee?: string;
93
- status?: string;
94
- type?: string;
95
- }
96
- interface StartTaskOptions {
97
- base?: string;
98
- force?: boolean;
99
- }
100
- interface PauseTaskOptions {
101
- message?: string;
102
- skipCommit?: boolean;
103
- }
104
- interface FinishTaskOptions {
105
- push?: boolean;
106
- skipUpdate?: boolean;
107
- }
108
- interface LintTasksOptions {
109
- path?: string;
110
- }
111
- /**
112
- * Validation issue found during task linting
113
- * Provider-agnostic - works with any task storage backend
114
- */
115
- interface TaskValidationIssue {
116
- message: string;
117
- severity: 'error' | 'warning';
118
- taskId: string;
119
- line?: number;
120
- }
121
- /**
122
- * Task metadata extracted from task source
123
- */
124
- interface TaskMetadata {
125
- assignee?: string;
126
- status?: string;
127
- type?: string;
128
- }
129
- /**
130
- * Result of task validation/linting operation
131
- */
132
- interface LintResult {
133
- errors: TaskValidationIssue[];
134
- tasksChecked: number;
135
- valid: boolean;
136
- warnings: TaskValidationIssue[];
137
- }
138
- /**
139
- * ITaskin - Core interface for task management system
140
- */
141
- interface ITaskin {
142
- /**
143
- * Finish a task
144
- */
145
- finish(taskId: TaskId, options?: FinishTaskOptions): Promise<void>;
146
- /**
147
- * Lint/validate tasks from the configured provider
148
- */
149
- lint(options?: LintTasksOptions): Promise<LintResult>;
150
- /**
151
- * List all tasks with optional filters
152
- */
153
- list(options?: ListTasksOptions): Promise<Task[]>;
154
- /**
155
- * Pause work on a task
156
- */
157
- pause(taskId: TaskId, options?: PauseTaskOptions): Promise<void>;
158
- /**
159
- * Start working on a task
160
- */
161
- start(taskId: TaskId, options?: StartTaskOptions): Promise<void>;
162
- }
163
- /**
164
- * Unique identifier for a task (UUID branded type).
165
- * Use this type for type-safe task ID handling across the system.
166
- *
167
- * @public
168
- * @example
169
- * ```ts
170
- * function getTask(id: TaskId): Task { ... }
171
- * const taskId: TaskId = '550e8400-e29b-41d4-a716-446655440000' as TaskId;
172
- * ```
173
- */
174
- type TaskId = z.infer<typeof TaskIdSchema>;
175
- /**
176
- * Type of task based on the kind of work being performed.
177
- * Follows conventional commit types for consistency across the codebase.
178
- *
179
- * @public
180
- */
181
- type TaskType = z.infer<typeof TaskTypeSchema>;
182
- /**
183
- * Represents a task in the Taskin system.
184
- * Contains all core information needed to track and manage work items.
185
- *
186
- * @public
187
- */
188
- type Task = z.infer<typeof TaskSchema>;
189
- /**
190
- * Represents a user in the Taskin system.
191
- * Contains basic user identification and contact information.
192
- *
193
- * @public
194
- */
195
- type User = z.infer<typeof UserSchema>;
196
-
197
- /**
198
- * A task with additional file system metadata.
199
- * Extends the base Task type with content and path information.
200
- * @public
201
- */
202
- type TaskFile = Task & {
203
- /** The raw markdown content of the task file */
204
- content: string;
205
- /** Absolute or relative path to the task file */
206
- filePath: string;
207
- /** The type of work this task represents */
208
- type: TaskType;
209
- /** Optional user assigned to this task */
210
- assignee?: User;
211
- };
212
- /**
213
- * Interface for task storage providers.
214
- * Implementations handle reading and writing tasks from different sources
215
- * (e.g., file system, database, API).
216
- * @public
217
- */
218
- interface ITaskProvider {
219
- /**
220
- * Find a specific task by its ID.
221
- * @param taskId - The unique identifier of the task
222
- * @returns The task if found, undefined otherwise
223
- */
224
- findTask(taskId: string): Promise<TaskFile | undefined>;
225
- /**
226
- * Retrieve all tasks from the provider.
227
- * @returns Array of all tasks
228
- */
229
- getAllTasks(): Promise<TaskFile[]>;
230
- /**
231
- * Update an existing task.
232
- * @param task - The task with updated information
233
- */
234
- updateTask(task: TaskFile): Promise<void>;
235
- }
236
- /**
237
- * Interface for task management operations.
238
- * Provides high-level methods for managing task workflow and state transitions.
239
- * @public
240
- */
241
- interface ITaskManager {
242
- /**
243
- * Mark a task as finished.
244
- * Transitions the task to 'done' status.
245
- * @param taskId - The unique identifier of the task
246
- * @returns The updated task
247
- * @throws Error if task is not found
248
- */
249
- finishTask(taskId: string): Promise<TaskFile>;
250
- /**
251
- * Start working on a task.
252
- * Transitions the task to 'in-progress' status.
253
- * @param taskId - The unique identifier of the task
254
- * @returns The updated task
255
- * @throws Error if task is not found, already in progress, or already done
256
- */
257
- startTask(taskId: string): Promise<TaskFile>;
258
- }
259
-
260
- declare class TaskManager implements ITaskManager {
261
- private taskProvider;
262
- constructor(taskProvider: ITaskProvider);
263
- startTask(taskId: string): Promise<TaskFile>;
264
- finishTask(taskId: string): Promise<TaskFile>;
265
- }
266
-
267
- declare class FileSystemTaskProvider implements ITaskProvider {
268
- private tasksDirectory;
269
- constructor(tasksDirectory: string);
270
- findTask(taskId: string): Promise<TaskFile | undefined>;
271
- updateTask(task: TaskFile): Promise<void>;
272
- getAllTasks(): Promise<TaskFile[]>;
273
- }
274
-
275
- /**
276
- * FileSystem task linter types
277
- * Re-exports from @opentask/taskin-types + FileSystem-specific extensions
278
- */
279
-
280
- /**
281
- * FileSystem-specific validation error
282
- * Extends TaskValidationIssue with file-specific information
283
- */
284
- interface FileValidationError extends Omit<TaskValidationIssue, 'taskId'> {
285
- file: string;
286
- }
287
- /**
288
- * FileSystem-specific lint result
289
- * Contains file-based validation results
290
- */
291
- interface FileLintResult {
292
- errors: FileValidationError[];
293
- filesChecked: number;
294
- valid: boolean;
295
- warnings: FileValidationError[];
296
- }
297
- /**
298
- * FileSystem-specific task linter interface
299
- * Defines validation operations for file-based task providers
300
- */
301
- interface IFileSystemTaskLinter {
302
- /**
303
- * Lint all task files in a directory
304
- * @param tasksDir - Directory containing task markdown files
305
- * @returns Validation results for all files
306
- */
307
- lintDirectory(tasksDir: string): Promise<FileLintResult>;
308
- /**
309
- * Lint a single task file
310
- * @param filePath - Path to the task markdown file
311
- * @returns Array of validation errors found in the file
312
- */
313
- lintFile(filePath: string): Promise<FileValidationError[]>;
314
- /**
315
- * Validate task metadata
316
- * @param metadata - Task metadata to validate
317
- * @param fileName - Name of the file being validated
318
- * @returns Array of validation errors
319
- */
320
- validateMetadata(metadata: Record<string, unknown>, fileName: string): FileValidationError[];
321
- }
322
-
323
- /**
324
- * FileSystem-specific task linter implementation
325
- * Validates task markdown files in the local filesystem
326
- */
327
- declare class FileSystemTaskLinter implements IFileSystemTaskLinter {
328
- private errors;
329
- private addError;
330
- /**
331
- * Validate task file name format (FileSystem-specific)
332
- */
333
- validateFileName(fileName: string): FileValidationError | null;
334
- private extractMetadata;
335
- /**
336
- * Validate task metadata (FileSystem-specific)
337
- */
338
- validateMetadata(metadata: TaskMetadata, filePath: string): FileValidationError[];
339
- private validateContent;
340
- /**
341
- * Lint a single task file (FileSystem-specific)
342
- */
343
- lintFile(filePath: string): Promise<FileValidationError[]>;
344
- /**
345
- * Lint all task files in the specified directory (FileSystem-specific)
346
- */
347
- lintDirectory(tasksDir: string): Promise<FileLintResult>;
348
- static printResults(result: FileLintResult): void;
349
- }
350
-
351
- /**
352
- * Taskin - Concrete implementation of ITaskin
353
- * Main class that implements task management with dependency injection
354
- */
355
-
356
- declare class Taskin implements ITaskin {
357
- private readonly taskProvider;
358
- private readonly taskManager;
359
- private readonly linter;
360
- constructor(taskProvider: FileSystemTaskProvider, taskManager: TaskManager, linter: FileSystemTaskLinter);
361
- list(options?: ListTasksOptions): Promise<Task[]>;
362
- start(taskId: TaskId, options?: StartTaskOptions): Promise<void>;
363
- pause(taskId: TaskId, options?: PauseTaskOptions): Promise<void>;
364
- finish(taskId: TaskId, _options?: FinishTaskOptions): Promise<void>;
365
- lint(options?: LintTasksOptions): Promise<LintResult>;
366
- }
367
-
368
- /**
369
- * Main entry point for Taskin CLI
370
- * Handles dependency injection and initialization
371
- */
372
-
373
- /**
374
- * Factory function to create a configured Taskin instance
375
- */
376
- declare function createTaskin(tasksDir?: string): Taskin;
377
- /**
378
- * Get the default Taskin instance
379
- * Uses current working directory TASKS folder
380
- */
381
- declare function getTaskin(): Taskin;
382
-
383
- export { type ITaskin, Taskin, createTaskin, getTaskin };