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.
@@ -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) {
@@ -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, exclude_assignees)
15
+ filters Configure filters (label, exclude_labels)
16
16
  teams Configure team selection
17
17
 
18
18
  Examples:
@@ -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[], excludeAssignees?: string[]): LocalConfig;
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.find((s) => s.name === "Todo")?.name ||
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.find((s) => s.name === "In Progress")?.name ||
64
+ findStatusByKeyword(states, ["in progress", "progress"]) ||
60
65
  "In Progress";
61
66
  const defaultDone = states.find((s) => s.type === "completed")?.name ||
62
- states.find((s) => s.name === "Done")?.name ||
67
+ findStatusByKeyword(states, ["done", "complete"]) ||
63
68
  "Done";
64
- const defaultTesting = states.find((s) => s.name === "Testing")?.name ||
65
- states.find((s) => s.name === "In Review")?.name;
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, excludeAssignees) {
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
  }
@@ -136,11 +136,16 @@ Examples:
136
136
  process.exit(1);
137
137
  }
138
138
  // Update local status
139
- if (newLocalStatus && newLocalStatus !== task.localStatus) {
140
- const oldStatus = task.localStatus;
141
- task.localStatus = newLocalStatus;
142
- await saveCycleData(data);
143
- console.log(`Local: ${task.id} ${oldStatus} → ${newLocalStatus}`);
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) {
@@ -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
@@ -92,7 +92,6 @@ export interface LocalConfig {
92
92
  current_user: string;
93
93
  team: string;
94
94
  teams?: string[];
95
- exclude_assignees?: string[];
96
95
  exclude_labels?: string[];
97
96
  label?: string;
98
97
  }
@@ -25,12 +25,11 @@ Examples:
25
25
  process.exit(1);
26
26
  }
27
27
  const localConfig = await loadLocalConfig();
28
- // Build excluded emails list from user keys
29
- const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
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" && !excludedEmails.has(t.assignee ?? ""))
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
  {
2
2
  "name": "team-toon-tack",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 `bun run init` to generate this interactively
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: user1
15
+ displayName: User One
16
16
  role: Developer
17
17
 
18
- labels:
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
- start_work: In Progress
75
- complete: Done
76
- need_review: In Review
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
- current_cycle:
79
- id: CYCLE_UUID
80
- name: Cycle #1
81
- start_date: 2026-01-01
82
- end_date: 2026-01-07
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
- # Filter issues by label (optional, defaults to first label in config)
9
+ # Primary team (required)
10
+ team: main
11
+
12
+ # Filter issues by label (optional)
9
13
  label: Frontend
10
14
 
11
- # Exclude issues assigned to these users (optional)
12
- # Use user keys from config.toon
13
- # exclude_assignees[1]: backend_dev
15
+ # Exclude issues with these labels (optional)
16
+ # exclude_labels[1]: Backend