team-toon-tack 3.4.1 → 3.5.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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Task management tools for Claude Code - supports Linear and Trello, efficient workflow without MCP overhead",
9
- "version": "1.0.1"
9
+ "version": "1.1.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "team-toon-tack",
14
14
  "source": "./",
15
15
  "description": "Linear/Trello task sync & management CLI with commands and skills",
16
- "version": "2.6.1"
16
+ "version": "2.7.0"
17
17
  }
18
18
  ]
19
19
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
3
  "description": "Linear/Trello task sync & management CLI for Claude Code - saves tokens vs MCP",
4
- "version": "2.6.1",
4
+ "version": "2.7.0",
5
5
  "author": {
6
6
  "name": "wayne930242",
7
7
  "email": "wayne930242@gmail.com"
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: ttt:assign
3
+ description: Reassign a Linear/Trello issue to a different user
4
+ arguments:
5
+ - name: issue-id
6
+ description: Issue ID to reassign (omit for current in-progress task)
7
+ required: false
8
+ - name: assignee
9
+ description: User key from config (omit for interactive select)
10
+ required: false
11
+ ---
12
+
13
+ <law>
14
+ YOU MUST execute the `ttt assign` command using the Bash tool.
15
+ DO NOT call Linear/Trello APIs directly — the CLI handles adapter selection and local cache updates.
16
+ </law>
17
+
18
+ # /ttt:assign — Reassign Issue
19
+
20
+ ## Execution
21
+
22
+ ```bash
23
+ ttt assign {{ issue-id }} -a {{ assignee }}
24
+ ```
25
+
26
+ ### Common Examples
27
+
28
+ ```bash
29
+ ttt assign MP-123 -a john # Assign MP-123 to john
30
+ ttt assign -a jane # Assign current in-progress task to jane
31
+ ttt assign MP-123 # Interactive assignee selection
32
+ ```
33
+
34
+ ## Full CLI Reference
35
+
36
+ ```
37
+ Usage: ttt assign [issue-id] [-a <user-key>]
38
+
39
+ Arguments:
40
+ issue-id Optional. Issue ID (omit for current task)
41
+
42
+ Options:
43
+ -a, --assignee <key> User key from config. If omitted, interactive select.
44
+ ```
45
+
46
+ ## After Execution
47
+
48
+ Report:
49
+ - Issue ID and new assignee display name
50
+ - Confirmation that local cache was updated
51
+
52
+ ## Error Handling
53
+
54
+ | Error | Solution |
55
+ |-------|----------|
56
+ | `No in-progress task found` | Specify issue ID explicitly |
57
+ | `User "<key>" not found` | Check key against `config.users` |
58
+ | `Issue not found` | Verify issue exists in Linear/Trello |
59
+ | `No source ID` | Issue has no remote ID — run `ttt sync` first |
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: ttt:cancel
3
+ description: Cancel a Linear/Trello issue (moves to Cancelled status)
4
+ arguments:
5
+ - name: issue-id
6
+ description: Issue ID to cancel (required)
7
+ required: true
8
+ - name: flags
9
+ description: "Optional: --yes to skip confirmation"
10
+ required: false
11
+ ---
12
+
13
+ <law>
14
+ YOU MUST execute the `ttt cancel` command using the Bash tool.
15
+ DO NOT call Linear/Trello APIs directly — the CLI handles adapter selection and cache removal.
16
+ Always confirm with the user before cancelling unless they explicitly pass `--yes`.
17
+ </law>
18
+
19
+ # /ttt:cancel — Cancel Issue
20
+
21
+ ## Execution
22
+
23
+ ```bash
24
+ ttt cancel {{ issue-id }} {{ flags }}
25
+ ```
26
+
27
+ ### Common Examples
28
+
29
+ ```bash
30
+ ttt cancel MP-123 # Cancel with interactive confirmation
31
+ ttt cancel MP-123 --yes # Cancel without confirmation prompt
32
+ ```
33
+
34
+ ## Full CLI Reference
35
+
36
+ ```
37
+ Usage: ttt cancel <issue-id> [--yes]
38
+
39
+ Arguments:
40
+ issue-id Issue ID to cancel (e.g., MP-123)
41
+
42
+ Options:
43
+ -y, --yes Skip confirmation prompt
44
+ ```
45
+
46
+ ## Behavior
47
+
48
+ - Finds the team's "Cancelled" workflow state automatically
49
+ - Removes the task from local `cycle.toon` if it was present
50
+ - Remote-only issues (not in local cache) are cancelled but no local change is made
51
+
52
+ ## After Execution
53
+
54
+ Report:
55
+ - Issue ID and cancellation confirmation
56
+ - Whether it was removed from local cycle data
57
+
58
+ ## Error Handling
59
+
60
+ | Error | Solution |
61
+ |-------|----------|
62
+ | `Issue ID is required` | Provide the issue ID as argument |
63
+ | `Issue not found` | Verify the ID exists in Linear/Trello |
64
+ | `No 'cancelled' workflow state found` | Team has no Cancelled state configured in Linear |
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: ttt:create
3
+ description: Create a new Linear/Trello issue and add to local cycle data
4
+ arguments:
5
+ - name: title
6
+ description: Issue title (omit to prompt interactively)
7
+ required: false
8
+ - name: flags
9
+ description: "Additional flags: -d <desc>, -a <assignee>, -p <0-4>, -l <labels>, -s <status>, --parent <id>"
10
+ required: false
11
+ ---
12
+
13
+ <law>
14
+ YOU MUST execute the `ttt create` command using the Bash tool.
15
+ DO NOT call Linear/Trello APIs directly — the CLI handles adapter selection and local cache updates.
16
+ DO NOT manually edit `.ttt/` files.
17
+ </law>
18
+
19
+ # /ttt:create — Create New Issue
20
+
21
+ ## Execution
22
+
23
+ ```bash
24
+ ttt create -t "{{ title }}" {{ flags }}
25
+ ```
26
+
27
+ If no title is provided, run `ttt create` alone for interactive mode.
28
+
29
+ ### Common Examples
30
+
31
+ ```bash
32
+ ttt create # Interactive mode
33
+ ttt create -t "Fix login bug" -p 2 # Quick create, high priority
34
+ ttt create -t "Subtask" --parent MP-100 # Create as child issue
35
+ ttt create -t "New" -a john -l frontend,bug # With assignee and labels
36
+ ```
37
+
38
+ ## Full CLI Reference
39
+
40
+ ```
41
+ Usage: ttt create [options]
42
+
43
+ Options:
44
+ -t, --title <text> Issue title (required unless interactive)
45
+ -d, --description <text> Description
46
+ -a, --assignee <key> Assignee user key from config
47
+ -p, --priority <0-4> Priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)
48
+ -l, --label <names> Label names (comma-separated)
49
+ -s, --status <name> Initial status name
50
+ --parent <id> Parent issue identifier (e.g., MP-100)
51
+ --no-interactive Skip interactive prompts
52
+ ```
53
+
54
+ ## After Execution
55
+
56
+ Report:
57
+ - Created issue ID and title
58
+ - URL if available
59
+ - Confirmation that local cycle data was updated
60
+
61
+ ## Error Handling
62
+
63
+ | Error | Solution |
64
+ |-------|----------|
65
+ | `Title is required when --no-interactive is set` | Provide `-t "title"` |
66
+ | `Priority must be 0-4` | Use valid priority value |
67
+ | `User "<key>" not found` | Check assignee key against `config.users` |
68
+ | `Parent issue "<id>" not found` | Verify parent exists in Linear/Trello |
69
+ | `Warning: label "<name>" not found` | Label not in config — will be skipped |
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: ttt:edit
3
+ description: Edit fields (title, description, priority, labels) of a Linear/Trello issue
4
+ arguments:
5
+ - name: issue-id
6
+ description: Issue ID to edit (omit for current in-progress task)
7
+ required: false
8
+ - name: flags
9
+ description: "Field flags: -t <title>, -d <desc>, -p <0-4>, -l <labels>"
10
+ required: false
11
+ ---
12
+
13
+ <law>
14
+ YOU MUST execute the `ttt edit` command using the Bash tool.
15
+ DO NOT call Linear/Trello APIs directly — the CLI handles adapter selection and cache refresh.
16
+ After a successful edit, the CLI automatically refreshes the local cycle cache from remote.
17
+ </law>
18
+
19
+ # /ttt:edit — Edit Issue Fields
20
+
21
+ ## Execution
22
+
23
+ ```bash
24
+ ttt edit {{ issue-id }} {{ flags }}
25
+ ```
26
+
27
+ If no flags are provided, interactive mode prompts you to choose which fields to change.
28
+
29
+ ### Common Examples
30
+
31
+ ```bash
32
+ ttt edit MP-123 -t "New title" # Update title only
33
+ ttt edit MP-123 -p 2 # Set priority to high
34
+ ttt edit MP-123 -l frontend,urgent # Replace labels (comma-separated)
35
+ ttt edit -t "Updated" -p 3 # Edit current task
36
+ ttt edit MP-123 # Interactive field selection
37
+ ```
38
+
39
+ ## Full CLI Reference
40
+
41
+ ```
42
+ Usage: ttt edit [issue-id] [options]
43
+
44
+ Arguments:
45
+ issue-id Optional. Issue ID (omit for current task)
46
+
47
+ Options:
48
+ -t, --title <text> New title
49
+ -d, --description <text> New description (empty string clears it)
50
+ -p, --priority <0-4> New priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)
51
+ -l, --label <names> Set labels (comma-separated, replaces existing)
52
+ --no-interactive Skip interactive prompts
53
+ ```
54
+
55
+ ## After Execution
56
+
57
+ Report:
58
+ - Issue ID and which fields were updated
59
+ - Confirmation that local cache was refreshed from remote
60
+
61
+ ## Error Handling
62
+
63
+ | Error | Solution |
64
+ |-------|----------|
65
+ | `Priority must be 0-4` | Use valid priority value |
66
+ | `No in-progress task` | Specify issue ID |
67
+ | `Issue not found` | Verify the ID exists in Linear/Trello |
68
+ | `Warning: label "<name>" not found` | Label missing from config — will be skipped |
69
+ | `Warning: remote update succeeded but failed to refresh local cache` | Run `ttt sync` to reconcile |
package/dist/bin/cli.js CHANGED
@@ -15,6 +15,10 @@ const COMMANDS = [
15
15
  "status",
16
16
  "show",
17
17
  "comment",
18
+ "create",
19
+ "assign",
20
+ "edit",
21
+ "cancel",
18
22
  "config",
19
23
  "help",
20
24
  "version",
@@ -35,6 +39,10 @@ COMMANDS:
35
39
  status Show or modify task status
36
40
  show Show issue details or search issues by filters
37
41
  comment Add a comment to an issue
42
+ create Create a new issue
43
+ assign Reassign an issue to a user
44
+ edit Edit issue fields (title, description, priority, labels)
45
+ cancel Cancel an issue
38
46
  config Configure settings (status mappings, filters)
39
47
  help Show this help message
40
48
  version Show version
@@ -141,6 +149,22 @@ async function main() {
141
149
  process.argv = ["node", "comment.js", ...commandArgs];
142
150
  await importScript("comment.js");
143
151
  break;
152
+ case "create":
153
+ process.argv = ["node", "create.js", ...commandArgs];
154
+ await importScript("create.js");
155
+ break;
156
+ case "assign":
157
+ process.argv = ["node", "assign.js", ...commandArgs];
158
+ await importScript("assign.js");
159
+ break;
160
+ case "edit":
161
+ process.argv = ["node", "edit.js", ...commandArgs];
162
+ await importScript("edit.js");
163
+ break;
164
+ case "cancel":
165
+ process.argv = ["node", "cancel.js", ...commandArgs];
166
+ await importScript("cancel.js");
167
+ break;
144
168
  case "config":
145
169
  process.argv = ["node", "config.js", ...commandArgs];
146
170
  await importScript("config.js");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bun
2
+ import { select } from "@inquirer/prompts";
3
+ import { createAdapter } from "./lib/adapters/index.js";
4
+ import { getSourceType, getTaskSourceId, loadConfig, loadCycleData, saveCycleData, } from "./utils.js";
5
+ function parseArgs(args) {
6
+ let issueId;
7
+ let assignee;
8
+ for (let i = 0; i < args.length; i++) {
9
+ const arg = args[i];
10
+ if (arg === "-a" || arg === "--assignee") {
11
+ assignee = args[++i];
12
+ }
13
+ else if (!arg.startsWith("-") && !issueId) {
14
+ issueId = arg;
15
+ }
16
+ }
17
+ return { issueId, assignee };
18
+ }
19
+ async function findTask(issueId, config) {
20
+ const cycleData = await loadCycleData();
21
+ if (!issueId) {
22
+ if (!cycleData) {
23
+ console.error("No cycle data found. Run ttt sync first.");
24
+ process.exit(1);
25
+ }
26
+ const inProgress = cycleData.tasks.find((t) => t.localStatus === "in-progress");
27
+ if (!inProgress) {
28
+ console.error("No in-progress task found. Specify issue ID.");
29
+ process.exit(1);
30
+ }
31
+ return inProgress;
32
+ }
33
+ const localTask = cycleData?.tasks.find((t) => t.id === issueId || t.id === issueId.toUpperCase());
34
+ if (localTask)
35
+ return localTask;
36
+ const adapter = createAdapter(config);
37
+ const issue = await adapter.searchIssue(issueId);
38
+ if (!issue) {
39
+ console.error(`Issue ${issueId} not found.`);
40
+ process.exit(1);
41
+ }
42
+ const sourceType = getSourceType(config);
43
+ return {
44
+ id: issue.id,
45
+ linearId: issue.sourceId,
46
+ sourceId: issue.sourceId,
47
+ sourceType,
48
+ title: issue.title,
49
+ status: issue.status,
50
+ localStatus: "pending",
51
+ assignee: issue.assigneeEmail,
52
+ priority: issue.priority,
53
+ labels: issue.labels,
54
+ url: issue.url,
55
+ };
56
+ }
57
+ async function assign() {
58
+ const args = process.argv.slice(2);
59
+ if (args.includes("--help") || args.includes("-h")) {
60
+ console.log(`Usage: ttt assign [issue-id] [-a <user-key>]
61
+
62
+ Reassign an issue to a different user.
63
+
64
+ Arguments:
65
+ issue-id Issue ID (e.g., MP-123). If omitted, uses current task.
66
+
67
+ Options:
68
+ -a, --assignee <key> User key from config. If omitted, interactive select.
69
+
70
+ Examples:
71
+ ttt assign MP-123 -a john # Assign MP-123 to john
72
+ ttt assign -a jane # Assign current task to jane
73
+ ttt assign MP-123 # Interactive assignee selection`);
74
+ process.exit(0);
75
+ }
76
+ const parsed = parseArgs(args);
77
+ const config = await loadConfig();
78
+ const task = await findTask(parsed.issueId, config);
79
+ let assigneeKey = parsed.assignee;
80
+ if (!assigneeKey) {
81
+ const choices = Object.entries(config.users).map(([key, u]) => ({
82
+ name: `${u.displayName} (${key})`,
83
+ value: key,
84
+ }));
85
+ assigneeKey = await select({
86
+ message: `Assign ${task.id} to:`,
87
+ choices,
88
+ });
89
+ }
90
+ const user = config.users[assigneeKey];
91
+ if (!user) {
92
+ console.error(`User "${assigneeKey}" not found.`);
93
+ console.error(`Available: ${Object.keys(config.users).join(", ")}`);
94
+ process.exit(1);
95
+ }
96
+ const sourceId = getTaskSourceId(task);
97
+ if (!sourceId) {
98
+ console.error(`No source ID for ${task.id}.`);
99
+ process.exit(1);
100
+ }
101
+ console.log(`Assigning ${task.id} to ${user.displayName}...`);
102
+ const adapter = createAdapter(config);
103
+ const result = await adapter.updateIssue(sourceId, { assigneeId: user.id });
104
+ if (!result.success) {
105
+ console.error(`Failed: ${result.error}`);
106
+ process.exit(1);
107
+ }
108
+ console.log(`\n✅ ${task.id} assigned to ${user.displayName}`);
109
+ const cycleData = await loadCycleData();
110
+ if (cycleData) {
111
+ const localTask = cycleData.tasks.find((t) => t.id === task.id);
112
+ if (localTask) {
113
+ localTask.assignee = user.email ?? user.displayName;
114
+ cycleData.updatedAt = new Date().toISOString();
115
+ await saveCycleData(cycleData);
116
+ console.log(` Local cache updated.`);
117
+ }
118
+ }
119
+ }
120
+ assign().catch(console.error);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bun
2
+ import { confirm } from "@inquirer/prompts";
3
+ import { createAdapter } from "./lib/adapters/index.js";
4
+ import { getTaskSourceId, loadConfig, loadCycleData, removeTaskFromCycleData, } from "./utils.js";
5
+ async function cancel() {
6
+ const args = process.argv.slice(2);
7
+ if (args.includes("--help") || args.includes("-h")) {
8
+ console.log(`Usage: ttt cancel <issue-id> [--yes]
9
+
10
+ Cancel an issue (moves to Cancelled status in Linear).
11
+
12
+ Arguments:
13
+ issue-id Issue ID (e.g., MP-123)
14
+
15
+ Options:
16
+ -y, --yes Skip confirmation prompt
17
+
18
+ Examples:
19
+ ttt cancel MP-123 # Cancel with confirmation
20
+ ttt cancel MP-123 --yes # Cancel without confirmation`);
21
+ process.exit(0);
22
+ }
23
+ const issueId = args.find((a) => !a.startsWith("-"));
24
+ const skipConfirm = args.includes("-y") || args.includes("--yes");
25
+ if (!issueId) {
26
+ console.error("Issue ID is required. Usage: ttt cancel <issue-id>");
27
+ process.exit(1);
28
+ }
29
+ const config = await loadConfig();
30
+ const cycleData = await loadCycleData();
31
+ const task = cycleData?.tasks.find((t) => t.id === issueId || t.id === issueId.toUpperCase());
32
+ const displayId = task?.id ?? issueId;
33
+ const displayTitle = task ? ` (${task.title})` : "";
34
+ if (!skipConfirm) {
35
+ const ok = await confirm({
36
+ message: `Cancel ${displayId}${displayTitle}?`,
37
+ default: false,
38
+ });
39
+ if (!ok) {
40
+ console.log("Aborted.");
41
+ process.exit(0);
42
+ }
43
+ }
44
+ let sourceId;
45
+ const adapter = createAdapter(config);
46
+ if (task) {
47
+ sourceId = getTaskSourceId(task);
48
+ }
49
+ else {
50
+ const issue = await adapter.searchIssue(issueId);
51
+ if (!issue) {
52
+ console.error(`Issue ${issueId} not found.`);
53
+ process.exit(1);
54
+ }
55
+ sourceId = issue.sourceId;
56
+ }
57
+ if (!sourceId) {
58
+ console.error(`No source ID for ${displayId}.`);
59
+ process.exit(1);
60
+ }
61
+ console.log(`Cancelling ${displayId}...`);
62
+ const result = await adapter.cancelIssue(sourceId);
63
+ if (!result.success) {
64
+ console.error(`Failed: ${result.error}`);
65
+ process.exit(1);
66
+ }
67
+ console.log(`\n✅ ${displayId} cancelled.`);
68
+ if (cycleData && task) {
69
+ await removeTaskFromCycleData(task.id, cycleData);
70
+ console.log(` Removed from local cycle data.`);
71
+ }
72
+ }
73
+ cancel().catch(console.error);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};