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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/commands/ttt-assign.md +59 -0
- package/commands/ttt-cancel.md +64 -0
- package/commands/ttt-create.md +69 -0
- package/commands/ttt-edit.md +69 -0
- package/dist/bin/cli.js +24 -0
- package/dist/scripts/assign.d.ts +2 -0
- package/dist/scripts/assign.js +120 -0
- package/dist/scripts/cancel.d.ts +2 -0
- package/dist/scripts/cancel.js +73 -0
- package/dist/scripts/create.d.ts +2 -0
- package/dist/scripts/create.js +210 -0
- package/dist/scripts/edit.d.ts +2 -0
- package/dist/scripts/edit.js +225 -0
- package/dist/scripts/lib/adapters/linear-adapter.d.ts +14 -1
- package/dist/scripts/lib/adapters/linear-adapter.js +102 -0
- package/dist/scripts/lib/adapters/trello-adapter.d.ts +14 -1
- package/dist/scripts/lib/adapters/trello-adapter.js +18 -0
- package/dist/scripts/lib/adapters/types.d.ts +48 -0
- package/dist/scripts/utils.d.ts +14 -0
- package/dist/scripts/utils.js +65 -0
- package/package.json +1 -1
- package/skills/managing-linear-tasks/SKILL.md +10 -1
|
@@ -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
|
|
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.
|
|
16
|
+
"version": "2.7.0"
|
|
17
17
|
}
|
|
18
18
|
]
|
|
19
19
|
}
|
|
@@ -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,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,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);
|