team-toon-tack 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/config/filters.js +0 -25
- package/dist/scripts/config/show.js +0 -1
- package/dist/scripts/config.js +1 -1
- package/dist/scripts/init.js +0 -2
- package/dist/scripts/lib/config-builder.d.ts +1 -1
- package/dist/scripts/lib/config-builder.js +11 -9
- package/dist/scripts/status.js +10 -5
- package/dist/scripts/sync.js +0 -8
- package/dist/scripts/utils.d.ts +0 -1
- package/dist/scripts/work-on.js +4 -5
- package/package.json +1 -1
- package/templates/config.example.toon +20 -65
- package/templates/local.example.toon +7 -4
|
@@ -9,10 +9,6 @@ export async function configureFilters(config, localConfig) {
|
|
|
9
9
|
filter: { team: { id: { eq: teamId } } },
|
|
10
10
|
});
|
|
11
11
|
const labels = labelsData.nodes;
|
|
12
|
-
// Fetch users
|
|
13
|
-
const team = await client.team(teamId);
|
|
14
|
-
const members = await team.members();
|
|
15
|
-
const users = members.nodes;
|
|
16
12
|
// Label filter (optional)
|
|
17
13
|
const labelChoices = [
|
|
18
14
|
{ title: "(No filter - sync all labels)", value: "" },
|
|
@@ -38,34 +34,13 @@ export async function configureFilters(config, localConfig) {
|
|
|
38
34
|
selected: localConfig.exclude_labels?.includes(l.name),
|
|
39
35
|
})),
|
|
40
36
|
});
|
|
41
|
-
// Exclude users
|
|
42
|
-
const excludeUsersResponse = await prompts({
|
|
43
|
-
type: "multiselect",
|
|
44
|
-
name: "excludeUsers",
|
|
45
|
-
message: "Select users to exclude (space to select):",
|
|
46
|
-
choices: users.map((u) => {
|
|
47
|
-
const key = (u.displayName || u.name || u.email?.split("@")[0] || "user")
|
|
48
|
-
.toLowerCase()
|
|
49
|
-
.replace(/[^a-z0-9]/g, "_");
|
|
50
|
-
return {
|
|
51
|
-
title: `${u.displayName || u.name} (${u.email})`,
|
|
52
|
-
value: key,
|
|
53
|
-
selected: localConfig.exclude_assignees?.includes(key),
|
|
54
|
-
};
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
37
|
localConfig.label = labelResponse.label || undefined;
|
|
58
38
|
localConfig.exclude_labels =
|
|
59
39
|
excludeLabelsResponse.excludeLabels?.length > 0
|
|
60
40
|
? excludeLabelsResponse.excludeLabels
|
|
61
41
|
: undefined;
|
|
62
|
-
localConfig.exclude_assignees =
|
|
63
|
-
excludeUsersResponse.excludeUsers?.length > 0
|
|
64
|
-
? excludeUsersResponse.excludeUsers
|
|
65
|
-
: undefined;
|
|
66
42
|
await saveLocalConfig(localConfig);
|
|
67
43
|
console.log("\n✅ Filters updated:");
|
|
68
44
|
console.log(` Label: ${localConfig.label || "(all)"}`);
|
|
69
45
|
console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
|
|
70
|
-
console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
|
|
71
46
|
}
|
|
@@ -15,7 +15,6 @@ export function showConfig(config, localConfig) {
|
|
|
15
15
|
console.log("\nFilters:");
|
|
16
16
|
console.log(` Label: ${localConfig.label || "(all)"}`);
|
|
17
17
|
console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
|
|
18
|
-
console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
|
|
19
18
|
// Status Mappings
|
|
20
19
|
console.log("\nStatus Mappings:");
|
|
21
20
|
if (config.status_transitions) {
|
package/dist/scripts/config.js
CHANGED
|
@@ -12,7 +12,7 @@ async function config() {
|
|
|
12
12
|
Subcommands:
|
|
13
13
|
show Show current configuration
|
|
14
14
|
status Configure status mappings (todo, in_progress, done, testing)
|
|
15
|
-
filters Configure filters (label, exclude_labels
|
|
15
|
+
filters Configure filters (label, exclude_labels)
|
|
16
16
|
teams Configure team selection
|
|
17
17
|
|
|
18
18
|
Examples:
|
package/dist/scripts/init.js
CHANGED
|
@@ -328,8 +328,6 @@ async function init() {
|
|
|
328
328
|
localConfig.teams = existingLocal.teams;
|
|
329
329
|
if (existingLocal.label)
|
|
330
330
|
localConfig.label = existingLocal.label;
|
|
331
|
-
if (existingLocal.exclude_assignees)
|
|
332
|
-
localConfig.exclude_assignees = existingLocal.exclude_assignees;
|
|
333
331
|
if (existingLocal.exclude_labels)
|
|
334
332
|
localConfig.exclude_labels = existingLocal.exclude_labels;
|
|
335
333
|
}
|
|
@@ -38,4 +38,4 @@ export declare function getDefaultStatusTransitions(states: LinearState[]): Stat
|
|
|
38
38
|
export declare function buildConfig(teams: LinearTeam[], users: LinearUser[], labels: LinearLabel[], states: LinearState[], statusTransitions: StatusTransitions, currentCycle?: LinearCycle): Config;
|
|
39
39
|
export declare function findUserKey(usersConfig: Record<string, UserConfig>, userId: string): string;
|
|
40
40
|
export declare function findTeamKey(teamsConfig: Record<string, TeamConfig>, teamId: string): string;
|
|
41
|
-
export declare function buildLocalConfig(currentUserKey: string, primaryTeamKey: string, selectedTeamKeys: string[], defaultLabel?: string, excludeLabels?: string[]
|
|
41
|
+
export declare function buildLocalConfig(currentUserKey: string, primaryTeamKey: string, selectedTeamKeys: string[], defaultLabel?: string, excludeLabels?: string[]): LocalConfig;
|
|
@@ -50,19 +50,24 @@ export function buildStatusesConfig(states) {
|
|
|
50
50
|
}
|
|
51
51
|
return config;
|
|
52
52
|
}
|
|
53
|
+
// Helper to find status by keyword (case insensitive)
|
|
54
|
+
function findStatusByKeyword(states, keywords) {
|
|
55
|
+
const lowerKeywords = keywords.map((k) => k.toLowerCase());
|
|
56
|
+
return states.find((s) => lowerKeywords.some((k) => s.name.toLowerCase().includes(k)))?.name;
|
|
57
|
+
}
|
|
53
58
|
export function getDefaultStatusTransitions(states) {
|
|
54
59
|
const defaultTodo = states.find((s) => s.type === "unstarted")?.name ||
|
|
55
|
-
states
|
|
60
|
+
findStatusByKeyword(states, ["todo", "pending"]) ||
|
|
56
61
|
states[0]?.name ||
|
|
57
62
|
"Todo";
|
|
58
63
|
const defaultInProgress = states.find((s) => s.type === "started")?.name ||
|
|
59
|
-
states
|
|
64
|
+
findStatusByKeyword(states, ["in progress", "progress"]) ||
|
|
60
65
|
"In Progress";
|
|
61
66
|
const defaultDone = states.find((s) => s.type === "completed")?.name ||
|
|
62
|
-
states
|
|
67
|
+
findStatusByKeyword(states, ["done", "complete"]) ||
|
|
63
68
|
"Done";
|
|
64
|
-
const defaultTesting = states
|
|
65
|
-
|
|
69
|
+
const defaultTesting = findStatusByKeyword(states, ["testing", "review"]) ||
|
|
70
|
+
undefined;
|
|
66
71
|
return {
|
|
67
72
|
todo: defaultTodo,
|
|
68
73
|
in_progress: defaultInProgress,
|
|
@@ -102,15 +107,12 @@ export function findTeamKey(teamsConfig, teamId) {
|
|
|
102
107
|
return (Object.entries(teamsConfig).find(([_, t]) => t.id === teamId)?.[0] ||
|
|
103
108
|
Object.keys(teamsConfig)[0]);
|
|
104
109
|
}
|
|
105
|
-
export function buildLocalConfig(currentUserKey, primaryTeamKey, selectedTeamKeys, defaultLabel, excludeLabels
|
|
110
|
+
export function buildLocalConfig(currentUserKey, primaryTeamKey, selectedTeamKeys, defaultLabel, excludeLabels) {
|
|
106
111
|
return {
|
|
107
112
|
current_user: currentUserKey,
|
|
108
113
|
team: primaryTeamKey,
|
|
109
114
|
teams: selectedTeamKeys.length > 1 ? selectedTeamKeys : undefined,
|
|
110
115
|
label: defaultLabel,
|
|
111
116
|
exclude_labels: excludeLabels && excludeLabels.length > 0 ? excludeLabels : undefined,
|
|
112
|
-
exclude_assignees: excludeAssignees && excludeAssignees.length > 0
|
|
113
|
-
? excludeAssignees
|
|
114
|
-
: undefined,
|
|
115
117
|
};
|
|
116
118
|
}
|
package/dist/scripts/status.js
CHANGED
|
@@ -136,11 +136,16 @@ Examples:
|
|
|
136
136
|
process.exit(1);
|
|
137
137
|
}
|
|
138
138
|
// Update local status
|
|
139
|
-
if (newLocalStatus
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
if (newLocalStatus) {
|
|
140
|
+
if (newLocalStatus !== task.localStatus) {
|
|
141
|
+
const oldStatus = task.localStatus;
|
|
142
|
+
task.localStatus = newLocalStatus;
|
|
143
|
+
await saveCycleData(data);
|
|
144
|
+
console.log(`Local: ${task.id} ${oldStatus} → ${newLocalStatus}`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.log(`Local: ${task.id} already ${newLocalStatus}`);
|
|
148
|
+
}
|
|
144
149
|
}
|
|
145
150
|
// Update Linear status
|
|
146
151
|
if (newLinearStatus || newLocalStatus) {
|
package/dist/scripts/sync.js
CHANGED
|
@@ -28,10 +28,6 @@ Examples:
|
|
|
28
28
|
const localConfig = await loadLocalConfig();
|
|
29
29
|
const client = getLinearClient();
|
|
30
30
|
const teamId = getTeamId(config, localConfig.team);
|
|
31
|
-
// Build excluded emails from local config
|
|
32
|
-
const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
|
|
33
|
-
.map((key) => config.users[key]?.email)
|
|
34
|
-
.filter(Boolean));
|
|
35
31
|
// Build excluded labels set
|
|
36
32
|
const excludedLabels = new Set(localConfig.exclude_labels ?? []);
|
|
37
33
|
// Phase 1: Fetch active cycle directly from team
|
|
@@ -139,10 +135,6 @@ Examples:
|
|
|
139
135
|
const issue = await client.issue(issueNode.id);
|
|
140
136
|
const assignee = await issue.assignee;
|
|
141
137
|
const assigneeEmail = assignee?.email;
|
|
142
|
-
// Skip excluded assignees
|
|
143
|
-
if (assigneeEmail && excludedEmails.has(assigneeEmail)) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
138
|
const labels = await issue.labels();
|
|
147
139
|
const labelNames = labels.nodes.map((l) => l.name);
|
|
148
140
|
// Skip if any label is in excluded list
|
package/dist/scripts/utils.d.ts
CHANGED
package/dist/scripts/work-on.js
CHANGED
|
@@ -25,12 +25,11 @@ Examples:
|
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
const localConfig = await loadLocalConfig();
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
.map((key) => config.users[key]?.email)
|
|
31
|
-
.filter(Boolean));
|
|
28
|
+
// Get current user email for filtering
|
|
29
|
+
const currentUserEmail = config.users[localConfig.current_user]?.email;
|
|
32
30
|
const pendingTasks = data.tasks
|
|
33
|
-
.filter((t) => t.localStatus === "pending" &&
|
|
31
|
+
.filter((t) => t.localStatus === "pending" &&
|
|
32
|
+
(!currentUserEmail || t.assignee === currentUserEmail))
|
|
34
33
|
.sort((a, b) => {
|
|
35
34
|
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
36
35
|
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Linear Team Configuration
|
|
2
2
|
# Copy this file to config.toon and fill in your team's data
|
|
3
|
-
# Run `
|
|
3
|
+
# Run `ttt init` to generate this interactively
|
|
4
4
|
|
|
5
5
|
teams:
|
|
6
6
|
main:
|
|
@@ -12,71 +12,26 @@ users:
|
|
|
12
12
|
user1:
|
|
13
13
|
id: USER1_UUID
|
|
14
14
|
email: user1@example.com
|
|
15
|
-
displayName:
|
|
15
|
+
displayName: User One
|
|
16
16
|
role: Developer
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
frontend:
|
|
20
|
-
id: LABEL_UUID
|
|
21
|
-
name: Frontend
|
|
22
|
-
color: "#f2c94c"
|
|
23
|
-
backend:
|
|
24
|
-
id: LABEL_UUID
|
|
25
|
-
name: Backend
|
|
26
|
-
color: "#5e6ad2"
|
|
27
|
-
bug:
|
|
28
|
-
id: LABEL_UUID
|
|
29
|
-
name: Bug
|
|
30
|
-
color: "#EB5757"
|
|
31
|
-
feature:
|
|
32
|
-
id: LABEL_UUID
|
|
33
|
-
name: Feature
|
|
34
|
-
color: "#BB87FC"
|
|
35
|
-
|
|
36
|
-
priorities:
|
|
37
|
-
urgent:
|
|
38
|
-
value: 1
|
|
39
|
-
name: Urgent
|
|
40
|
-
high:
|
|
41
|
-
value: 2
|
|
42
|
-
name: High
|
|
43
|
-
medium:
|
|
44
|
-
value: 3
|
|
45
|
-
name: Medium
|
|
46
|
-
low:
|
|
47
|
-
value: 4
|
|
48
|
-
name: Low
|
|
49
|
-
|
|
50
|
-
statuses:
|
|
51
|
-
backlog:
|
|
52
|
-
name: Backlog
|
|
53
|
-
type: backlog
|
|
54
|
-
todo:
|
|
55
|
-
name: Todo
|
|
56
|
-
type: unstarted
|
|
57
|
-
in_progress:
|
|
58
|
-
name: In Progress
|
|
59
|
-
type: started
|
|
60
|
-
in_review:
|
|
61
|
-
name: In Review
|
|
62
|
-
type: started
|
|
63
|
-
testing:
|
|
64
|
-
name: Testing
|
|
65
|
-
type: started
|
|
66
|
-
done:
|
|
67
|
-
name: Done
|
|
68
|
-
type: completed
|
|
69
|
-
canceled:
|
|
70
|
-
name: Canceled
|
|
71
|
-
type: canceled
|
|
72
|
-
|
|
18
|
+
# Status mapping for Linear integration
|
|
73
19
|
status_transitions:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
20
|
+
todo: Todo
|
|
21
|
+
in_progress: In Progress
|
|
22
|
+
done: Done
|
|
23
|
+
testing: Testing
|
|
24
|
+
|
|
25
|
+
# Priority ordering (optional, defaults shown)
|
|
26
|
+
priority_order[1]: urgent
|
|
27
|
+
priority_order[2]: high
|
|
28
|
+
priority_order[3]: medium
|
|
29
|
+
priority_order[4]: low
|
|
30
|
+
priority_order[5]: none
|
|
77
31
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
32
|
+
# Current cycle info (auto-managed by sync)
|
|
33
|
+
# current_cycle:
|
|
34
|
+
# id: CYCLE_UUID
|
|
35
|
+
# name: Cycle #1
|
|
36
|
+
# start_date: 2026-01-01
|
|
37
|
+
# end_date: 2026-01-07
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
# This file is gitignored and contains personal settings
|
|
4
4
|
|
|
5
5
|
# Your user key (must match a key in config.toon users section)
|
|
6
|
+
# Only tasks assigned to this user will be synced
|
|
6
7
|
current_user: user1
|
|
7
8
|
|
|
8
|
-
#
|
|
9
|
+
# Primary team (required)
|
|
10
|
+
team: main
|
|
11
|
+
|
|
12
|
+
# Filter issues by label (optional)
|
|
9
13
|
label: Frontend
|
|
10
14
|
|
|
11
|
-
# Exclude issues
|
|
12
|
-
#
|
|
13
|
-
# exclude_assignees[1]: backend_dev
|
|
15
|
+
# Exclude issues with these labels (optional)
|
|
16
|
+
# exclude_labels[1]: Backend
|