team-toon-tack 1.0.12 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
- import prompts from 'prompts';
2
- import { getLinearClient, loadConfig, loadLocalConfig, loadCycleData, saveCycleData, getUserEmail, getTeamId, getPrioritySortIndex } from './utils.js';
1
+ import prompts from "prompts";
2
+ import { getLinearClient, getPrioritySortIndex, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
3
3
  const PRIORITY_LABELS = {
4
- 0: '⚪ None',
5
- 1: '🔴 Urgent',
6
- 2: '🟠 High',
7
- 3: '🟡 Medium',
8
- 4: '🟢 Low'
4
+ 0: "⚪ None",
5
+ 1: "🔴 Urgent",
6
+ 2: "🟠 High",
7
+ 3: "🟡 Medium",
8
+ 4: "🟢 Low",
9
9
  };
10
10
  async function workOn() {
11
11
  const args = process.argv.slice(2);
12
12
  // Handle help flag
13
- if (args.includes('--help') || args.includes('-h')) {
13
+ if (args.includes("--help") || args.includes("-h")) {
14
14
  console.log(`Usage: ttt work-on [issue-id]
15
15
 
16
16
  Arguments:
@@ -27,18 +27,16 @@ Examples:
27
27
  const config = await loadConfig();
28
28
  const data = await loadCycleData();
29
29
  if (!data) {
30
- console.error('No cycle data found. Run /sync-linear first.');
30
+ console.error("No cycle data found. Run /sync-linear first.");
31
31
  process.exit(1);
32
32
  }
33
- const userEmail = await getUserEmail();
34
33
  const localConfig = await loadLocalConfig();
35
34
  // Build excluded emails list from user keys
36
35
  const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
37
- .map(key => config.users[key]?.email)
36
+ .map((key) => config.users[key]?.email)
38
37
  .filter(Boolean));
39
38
  const pendingTasks = data.tasks
40
- .filter(t => t.localStatus === 'pending' &&
41
- !excludedEmails.has(t.assignee ?? ''))
39
+ .filter((t) => t.localStatus === "pending" && !excludedEmails.has(t.assignee ?? ""))
42
40
  .sort((a, b) => {
43
41
  const pa = getPrioritySortIndex(a.priority, config.priority_order);
44
42
  const pb = getPrioritySortIndex(b.priority, config.priority_order);
@@ -48,52 +46,52 @@ Examples:
48
46
  if (!issueId) {
49
47
  // Interactive selection
50
48
  if (pendingTasks.length === 0) {
51
- console.log('✅ 沒有待處理的任務,所有工作已完成或進行中');
49
+ console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
52
50
  process.exit(0);
53
51
  }
54
- const choices = pendingTasks.map(task => ({
55
- title: `${PRIORITY_LABELS[task.priority] || ''} ${task.id}: ${task.title}`,
52
+ const choices = pendingTasks.map((task) => ({
53
+ title: `${PRIORITY_LABELS[task.priority] || ""} ${task.id}: ${task.title}`,
56
54
  value: task.id,
57
- description: task.labels.join(', ')
55
+ description: task.labels.join(", "),
58
56
  }));
59
57
  const response = await prompts({
60
- type: 'select',
61
- name: 'issueId',
62
- message: '選擇要處理的任務:',
63
- choices: choices
58
+ type: "select",
59
+ name: "issueId",
60
+ message: "選擇要處理的任務:",
61
+ choices: choices,
64
62
  });
65
63
  if (!response.issueId) {
66
- console.log('已取消');
64
+ console.log("已取消");
67
65
  process.exit(0);
68
66
  }
69
67
  issueId = response.issueId;
70
68
  }
71
- else if (['next', '下一個', '下一個工作'].includes(issueId)) {
69
+ else if (["next", "下一個", "下一個工作"].includes(issueId)) {
72
70
  // Auto-select highest priority
73
71
  if (pendingTasks.length === 0) {
74
- console.log('✅ 沒有待處理的任務,所有工作已完成或進行中');
72
+ console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
75
73
  process.exit(0);
76
74
  }
77
75
  issueId = pendingTasks[0].id;
78
76
  console.log(`Auto-selected: ${issueId}`);
79
77
  }
80
78
  // Phase 1: Find task
81
- const task = data.tasks.find(t => t.id === issueId || t.id === `MP-${issueId}`);
79
+ const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
82
80
  if (!task) {
83
81
  console.error(`Issue ${issueId} not found in current cycle.`);
84
82
  process.exit(1);
85
83
  }
86
84
  // Phase 2: Availability Check
87
- if (task.localStatus === 'in-progress') {
85
+ if (task.localStatus === "in-progress") {
88
86
  console.log(`⚠️ 此任務 ${task.id} 已在進行中`);
89
87
  }
90
- else if (task.localStatus === 'completed') {
88
+ else if (task.localStatus === "completed") {
91
89
  console.log(`⚠️ 此任務 ${task.id} 已完成`);
92
90
  process.exit(0);
93
91
  }
94
92
  // Phase 3: Mark as In Progress
95
- if (task.localStatus === 'pending') {
96
- task.localStatus = 'in-progress';
93
+ if (task.localStatus === "pending") {
94
+ task.localStatus = "in-progress";
97
95
  await saveCycleData(data);
98
96
  console.log(`Local: ${task.id} → in-progress`);
99
97
  // Update Linear using stored linearId
@@ -101,26 +99,30 @@ Examples:
101
99
  try {
102
100
  const client = getLinearClient();
103
101
  const workflowStates = await client.workflowStates({
104
- filter: { team: { id: { eq: getTeamId(config, localConfig.team) } } }
102
+ filter: { team: { id: { eq: getTeamId(config, localConfig.team) } } },
105
103
  });
106
- const inProgressState = workflowStates.nodes.find(s => s.name === 'In Progress');
104
+ // Get status name from config or use default
105
+ const inProgressStatusName = config.status_transitions?.in_progress || "In Progress";
106
+ const inProgressState = workflowStates.nodes.find((s) => s.name === inProgressStatusName);
107
107
  if (inProgressState) {
108
- await client.updateIssue(task.linearId, { stateId: inProgressState.id });
109
- console.log(`Linear: ${task.id} → In Progress`);
108
+ await client.updateIssue(task.linearId, {
109
+ stateId: inProgressState.id,
110
+ });
111
+ console.log(`Linear: ${task.id} → ${inProgressStatusName}`);
110
112
  }
111
113
  }
112
114
  catch (e) {
113
- console.error('Failed to update Linear:', e);
115
+ console.error("Failed to update Linear:", e);
114
116
  }
115
117
  }
116
118
  }
117
119
  // Phase 4: Display task info
118
- console.log(`\n${''.repeat(50)}`);
120
+ console.log(`\n${"".repeat(50)}`);
119
121
  console.log(`👷 ${task.id}: ${task.title}`);
120
- console.log(`${''.repeat(50)}`);
121
- console.log(`Priority: ${PRIORITY_LABELS[task.priority] || 'None'}`);
122
- console.log(`Labels: ${task.labels.join(', ')}`);
123
- console.log(`Branch: ${task.branch || 'N/A'}`);
122
+ console.log(`${"".repeat(50)}`);
123
+ console.log(`Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
124
+ console.log(`Labels: ${task.labels.join(", ")}`);
125
+ console.log(`Branch: ${task.branch || "N/A"}`);
124
126
  if (task.url)
125
127
  console.log(`URL: ${task.url}`);
126
128
  if (task.description) {
@@ -132,7 +134,19 @@ Examples:
132
134
  console.log(` - ${att.title}: ${att.url}`);
133
135
  }
134
136
  }
135
- console.log(`\n${'─'.repeat(50)}`);
136
- console.log('Next: bun type-check && bun lint');
137
+ if (task.comments && task.comments.length > 0) {
138
+ console.log(`\n💬 Comments (${task.comments.length}):`);
139
+ for (const comment of task.comments) {
140
+ const date = new Date(comment.createdAt).toLocaleDateString();
141
+ console.log(`\n [${comment.user || "Unknown"} - ${date}]`);
142
+ // Indent comment body
143
+ const lines = comment.body.split("\n");
144
+ for (const line of lines) {
145
+ console.log(` ${line}`);
146
+ }
147
+ }
148
+ }
149
+ console.log(`\n${"─".repeat(50)}`);
150
+ console.log("Next: bun type-check && bun lint");
137
151
  }
138
152
  workOn().catch(console.error);
package/package.json CHANGED
@@ -1,52 +1,54 @@
1
1
  {
2
- "name": "team-toon-tack",
3
- "version": "1.0.12",
4
- "description": "Linear task sync & management CLI with TOON format",
5
- "type": "module",
6
- "bin": {
7
- "ttt": "./bin/cli.js",
8
- "team-toon-tack": "./bin/cli.js"
9
- },
10
- "exports": {
11
- ".": "./scripts/utils.js",
12
- "./utils": "./scripts/utils.js"
13
- },
14
- "files": [
15
- "bin",
16
- "scripts",
17
- "templates",
18
- "package.json"
19
- ],
20
- "scripts": {
21
- "build": "tsc",
22
- "prepublishOnly": "npm run build",
23
- "release": "npm config set //registry.npmjs.org/:_authToken=$NPM_PUBLISH_KEY && npm publish"
24
- },
25
- "dependencies": {
26
- "@linear/sdk": "^29.0.0",
27
- "@toon-format/toon": "^2.0.0",
28
- "prompts": "^2.4.2"
29
- },
30
- "devDependencies": {
31
- "@types/node": "^20.0.0",
32
- "@types/prompts": "^2.4.9",
33
- "typescript": "^5.0.0"
34
- },
35
- "keywords": [
36
- "linear",
37
- "task-management",
38
- "cli",
39
- "toon",
40
- "sync",
41
- "project-management"
42
- ],
43
- "author": "wayne930242",
44
- "license": "MIT",
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/wayne930242/team-toon-tack.git"
48
- },
49
- "engines": {
50
- "node": ">=18.0.0"
51
- }
2
+ "name": "team-toon-tack",
3
+ "version": "1.6.0",
4
+ "description": "Linear task sync & management CLI with TOON format",
5
+ "type": "module",
6
+ "bin": {
7
+ "ttt": "./dist/bin/cli.js",
8
+ "team-toon-tack": "./dist/bin/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": "./dist/scripts/utils.js",
12
+ "./utils": "./dist/scripts/utils.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "templates"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "lint": "biome lint .",
21
+ "format": "biome format --write .",
22
+ "type": "tsc --noEmit",
23
+ "prepublishOnly": "npm run build",
24
+ "release": "npm config set //registry.npmjs.org/:_authToken=$NPM_PUBLISH_KEY && npm publish"
25
+ },
26
+ "dependencies": {
27
+ "@linear/sdk": "^69.0.0",
28
+ "@toon-format/toon": "^2.1.0",
29
+ "prompts": "^2.4.2"
30
+ },
31
+ "devDependencies": {
32
+ "@biomejs/biome": "^2.3.11",
33
+ "@types/node": "^25.0.3",
34
+ "@types/prompts": "^2.4.9",
35
+ "typescript": "^5.9.3"
36
+ },
37
+ "keywords": [
38
+ "linear",
39
+ "task-management",
40
+ "cli",
41
+ "toon",
42
+ "sync",
43
+ "project-management"
44
+ ],
45
+ "author": "wayne930242",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/wayne930242/team-toon-tack.git"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
52
54
  }
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: done-job
3
+ description: Mark a Linear issue as done with AI summary comment
4
+ arguments:
5
+ - name: issue-id
6
+ description: Linear issue ID (e.g., MP-624). Optional if only one task is in-progress
7
+ required: false
8
+ ---
9
+
10
+ # Complete Task
11
+
12
+ Mark a task as done and update Linear with commit details.
13
+
14
+ ## Process
15
+
16
+ ### 1. Determine Issue ID
17
+
18
+ Check `.toon/cycle.toon` for tasks with `localStatus: in-progress`.
19
+
20
+ ### 2. Write Fix Summary
21
+
22
+ Prepare a concise summary (1-3 sentences) covering:
23
+ - Root cause
24
+ - How it was resolved
25
+ - Key code changes
26
+
27
+ ### 3. Run Command
28
+
29
+ ```bash
30
+ ttt done -d .ttt $ARGUMENTS -m "修復說明"
31
+ ```
32
+
33
+ ## What It Does
34
+
35
+ - Linear issue status → "Done"
36
+ - Adds comment with commit hash, message, and diff summary
37
+ - Parent issue (if exists) → "Testing"
38
+ - Local status → `completed` in `.toon/cycle.toon`
39
+
40
+ ## Example Usage
41
+
42
+ ```
43
+ /done-job MP-624
44
+ /done-job
45
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: sync-linear
3
+ description: Sync Linear Frontend issues to local TOON file
4
+ ---
5
+
6
+ # Sync Linear Issues
7
+
8
+ Fetch current cycle's Frontend issues from Linear to `.toon/cycle.toon`.
9
+
10
+ ## Process
11
+
12
+ ### 1. Run Sync
13
+
14
+ ```bash
15
+ ttt sync -d .ttt
16
+ ```
17
+
18
+ ### 2. Review Output
19
+
20
+ Script displays a summary of tasks in the current cycle.
21
+
22
+ ## When to Use
23
+
24
+ - Before starting a new work session
25
+ - When task list is missing or outdated
26
+ - After issues are updated in Linear
27
+
28
+ ## Example Usage
29
+
30
+ ```
31
+ /sync-linear
32
+ ```
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: work-on
3
+ description: Select and start working on a Linear issue
4
+ arguments:
5
+ - name: issue-id
6
+ description: "Issue ID (e.g., MP-624) or 'next' for auto-select"
7
+ required: false
8
+ ---
9
+
10
+ # Start Working on Issue
11
+
12
+ Select a task and update status to "In Progress" on both local and Linear.
13
+
14
+ ## Process
15
+
16
+ ### 1. Run Command
17
+
18
+ ```bash
19
+ ttt work-on -d .ttt $ARGUMENTS
20
+ ```
21
+
22
+ ### 2. Review Issue Details
23
+
24
+ Script displays title, description, priority, labels, and attachments.
25
+
26
+ ### 3. Implement
27
+
28
+ 1. Read the issue description carefully
29
+ 2. Explore related code
30
+ 3. Implement the fix/feature
31
+ 4. Run `bun type-check && bun lint`
32
+ 5. Commit with conventional format
33
+ 6. Use `/done-job` to complete
34
+
35
+ ## Example Usage
36
+
37
+ ```
38
+ /work-on
39
+ /work-on MP-624
40
+ /work-on next
41
+ ```
package/bin/cli.ts DELETED
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env node
2
- import { resolve } from 'node:path';
3
- import { readFileSync } from 'node:fs';
4
- import { fileURLToPath } from 'node:url';
5
- import { dirname, join } from 'node:path';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
9
- const VERSION = pkg.version;
10
-
11
- const COMMANDS = ['init', 'sync', 'work-on', 'done', 'help', 'version'] as const;
12
- type Command = typeof COMMANDS[number];
13
-
14
- function printHelp() {
15
- console.log(`
16
- team-toon-tack (ttt) - Linear task sync & management CLI
17
-
18
- USAGE:
19
- ttt <command> [options]
20
-
21
- COMMANDS:
22
- init Initialize config files in current directory
23
- sync Sync issues from Linear to local cycle.ttt
24
- work-on Start working on a task (interactive or by ID)
25
- done Mark current task as completed
26
- help Show this help message
27
- version Show version
28
-
29
- GLOBAL OPTIONS:
30
- -d, --dir <path> Config directory (default: .ttt)
31
- Can also set via TOON_DIR environment variable
32
-
33
- EXAMPLES:
34
- ttt init # Initialize .ttt directory
35
- ttt init -d ./custom # Initialize in custom directory
36
- ttt sync # Sync from Linear
37
- ttt work-on # Interactive task selection
38
- ttt work-on MP-123 # Work on specific issue
39
- ttt work-on next # Auto-select highest priority
40
- ttt done # Complete current task
41
- ttt done -m "Fixed the bug" # With completion message
42
-
43
- ENVIRONMENT:
44
- LINEAR_API_KEY Required. Your Linear API key
45
- TOON_DIR Optional. Default config directory
46
-
47
- More info: https://github.com/wayne930242/team-toon-tack
48
- `);
49
- }
50
-
51
- function printVersion() {
52
- console.log(`team-toon-tack v${VERSION}`);
53
- }
54
-
55
- function parseGlobalArgs(args: string[]): { dir: string; commandArgs: string[] } {
56
- let dir = process.env.TOON_DIR || resolve(process.cwd(), '.ttt');
57
- const commandArgs: string[] = [];
58
-
59
- for (let i = 0; i < args.length; i++) {
60
- const arg = args[i];
61
- if (arg === '-d' || arg === '--dir') {
62
- dir = resolve(args[++i] || '.');
63
- } else {
64
- commandArgs.push(arg);
65
- }
66
- }
67
-
68
- return { dir, commandArgs };
69
- }
70
-
71
- async function main() {
72
- const args = process.argv.slice(2);
73
-
74
- if (args.length === 0 || args[0] === 'help' || args[0] === '-h' || args[0] === '--help') {
75
- printHelp();
76
- process.exit(0);
77
- }
78
-
79
- if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
80
- printVersion();
81
- process.exit(0);
82
- }
83
-
84
- const command = args[0] as Command;
85
- const restArgs = args.slice(1);
86
- const { dir, commandArgs } = parseGlobalArgs(restArgs);
87
-
88
- // Set TOON_DIR for scripts to use
89
- process.env.TOON_DIR = dir;
90
-
91
- if (!COMMANDS.includes(command)) {
92
- console.error(`Unknown command: ${command}`);
93
- console.error(`Run 'ttt help' for usage.`);
94
- process.exit(1);
95
- }
96
-
97
- // Import and run the appropriate script
98
- const scriptDir = new URL('../scripts/', import.meta.url).pathname;
99
- try {
100
- switch (command) {
101
- case 'init':
102
- process.argv = ['node', 'init.js', ...commandArgs];
103
- await import(`${scriptDir}init.js`);
104
- break;
105
- case 'sync':
106
- await import(`${scriptDir}sync.js`);
107
- break;
108
- case 'work-on':
109
- process.argv = ['node', 'work-on.js', ...commandArgs];
110
- await import(`${scriptDir}work-on.js`);
111
- break;
112
- case 'done':
113
- process.argv = ['node', 'done-job.js', ...commandArgs];
114
- await import(`${scriptDir}done-job.js`);
115
- break;
116
- }
117
- } catch (error) {
118
- if (error instanceof Error) {
119
- console.error(`Error: ${error.message}`);
120
- }
121
- process.exit(1);
122
- }
123
- }
124
-
125
- main();