team-toon-tack 1.6.0 → 1.6.1

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.
@@ -0,0 +1,2 @@
1
+ import { type Config, type LocalConfig } from "../utils.js";
2
+ export declare function configureFilters(config: Config, localConfig: LocalConfig): Promise<void>;
@@ -0,0 +1,71 @@
1
+ import prompts from "prompts";
2
+ import { getLinearClient, getTeamId, saveLocalConfig, } from "../utils.js";
3
+ export async function configureFilters(config, localConfig) {
4
+ console.log("šŸ” Configure Filters\n");
5
+ const client = getLinearClient();
6
+ const teamId = getTeamId(config, localConfig.team);
7
+ // Fetch labels
8
+ const labelsData = await client.issueLabels({
9
+ filter: { team: { id: { eq: teamId } } },
10
+ });
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
+ // Label filter (optional)
17
+ const labelChoices = [
18
+ { title: "(No filter - sync all labels)", value: "" },
19
+ ...labels.map((l) => ({ title: l.name, value: l.name })),
20
+ ];
21
+ const labelResponse = await prompts({
22
+ type: "select",
23
+ name: "label",
24
+ message: "Select label filter (optional):",
25
+ choices: labelChoices,
26
+ initial: localConfig.label
27
+ ? labelChoices.findIndex((c) => c.value === localConfig.label)
28
+ : 0,
29
+ });
30
+ // Exclude labels
31
+ const excludeLabelsResponse = await prompts({
32
+ type: "multiselect",
33
+ name: "excludeLabels",
34
+ message: "Select labels to exclude (space to select):",
35
+ choices: labels.map((l) => ({
36
+ title: l.name,
37
+ value: l.name,
38
+ selected: localConfig.exclude_labels?.includes(l.name),
39
+ })),
40
+ });
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
+ localConfig.label = labelResponse.label || undefined;
58
+ localConfig.exclude_labels =
59
+ excludeLabelsResponse.excludeLabels?.length > 0
60
+ ? excludeLabelsResponse.excludeLabels
61
+ : undefined;
62
+ localConfig.exclude_assignees =
63
+ excludeUsersResponse.excludeUsers?.length > 0
64
+ ? excludeUsersResponse.excludeUsers
65
+ : undefined;
66
+ await saveLocalConfig(localConfig);
67
+ console.log("\nāœ… Filters updated:");
68
+ console.log(` Label: ${localConfig.label || "(all)"}`);
69
+ console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
70
+ console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
71
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config, LocalConfig } from "../utils.js";
2
+ export declare function showConfig(config: Config, localConfig: LocalConfig): void;
@@ -0,0 +1,33 @@
1
+ export function showConfig(config, localConfig) {
2
+ console.log("šŸ“‹ Current Configuration:\n");
3
+ // Teams
4
+ console.log("Teams:");
5
+ if (localConfig.teams && localConfig.teams.length > 0) {
6
+ console.log(` Selected: ${localConfig.teams.join(", ")}`);
7
+ console.log(` Primary: ${localConfig.team}`);
8
+ }
9
+ else {
10
+ console.log(` ${localConfig.team}`);
11
+ }
12
+ // User
13
+ console.log(`\nUser: ${localConfig.current_user}`);
14
+ // Filters
15
+ console.log("\nFilters:");
16
+ console.log(` Label: ${localConfig.label || "(all)"}`);
17
+ console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
18
+ console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
19
+ // Status Mappings
20
+ console.log("\nStatus Mappings:");
21
+ if (config.status_transitions) {
22
+ const st = config.status_transitions;
23
+ console.log(` Todo: ${st.todo}`);
24
+ console.log(` In Progress: ${st.in_progress}`);
25
+ console.log(` Done: ${st.done}`);
26
+ if (st.testing) {
27
+ console.log(` Testing: ${st.testing}`);
28
+ }
29
+ }
30
+ else {
31
+ console.log(" (not configured)");
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ import { type Config, type LocalConfig } from "../utils.js";
2
+ export declare function configureStatus(config: Config, localConfig: LocalConfig): Promise<void>;
@@ -0,0 +1,73 @@
1
+ import prompts from "prompts";
2
+ import { getWorkflowStates } from "../lib/linear.js";
3
+ import { saveConfig, } from "../utils.js";
4
+ export async function configureStatus(config, localConfig) {
5
+ console.log("šŸ“Š Configure Status Mappings\n");
6
+ const states = await getWorkflowStates(config, localConfig.team);
7
+ if (states.length === 0) {
8
+ console.error("No workflow states found for this team.");
9
+ process.exit(1);
10
+ }
11
+ const stateChoices = states.map((s) => ({
12
+ title: `${s.name} (${s.type})`,
13
+ value: s.name,
14
+ }));
15
+ // Get current values or defaults
16
+ const current = config.status_transitions || {};
17
+ const defaultTodo = current.todo || states.find((s) => s.type === "unstarted")?.name || "Todo";
18
+ const defaultInProgress = current.in_progress ||
19
+ states.find((s) => s.type === "started")?.name ||
20
+ "In Progress";
21
+ const defaultDone = current.done || states.find((s) => s.type === "completed")?.name || "Done";
22
+ const defaultTesting = current.testing || states.find((s) => s.name === "Testing")?.name;
23
+ const todoResponse = await prompts({
24
+ type: "select",
25
+ name: "todo",
26
+ message: 'Select status for "Todo" (pending tasks):',
27
+ choices: stateChoices,
28
+ initial: stateChoices.findIndex((c) => c.value === defaultTodo),
29
+ });
30
+ const inProgressResponse = await prompts({
31
+ type: "select",
32
+ name: "in_progress",
33
+ message: 'Select status for "In Progress" (working tasks):',
34
+ choices: stateChoices,
35
+ initial: stateChoices.findIndex((c) => c.value === defaultInProgress),
36
+ });
37
+ const doneResponse = await prompts({
38
+ type: "select",
39
+ name: "done",
40
+ message: 'Select status for "Done" (completed tasks):',
41
+ choices: stateChoices,
42
+ initial: stateChoices.findIndex((c) => c.value === defaultDone),
43
+ });
44
+ // Testing is optional
45
+ const testingChoices = [
46
+ { title: "(None)", value: undefined },
47
+ ...stateChoices,
48
+ ];
49
+ const testingResponse = await prompts({
50
+ type: "select",
51
+ name: "testing",
52
+ message: 'Select status for "Testing" (optional, for parent tasks):',
53
+ choices: testingChoices,
54
+ initial: defaultTesting
55
+ ? testingChoices.findIndex((c) => c.value === defaultTesting)
56
+ : 0,
57
+ });
58
+ const statusTransitions = {
59
+ todo: todoResponse.todo || defaultTodo,
60
+ in_progress: inProgressResponse.in_progress || defaultInProgress,
61
+ done: doneResponse.done || defaultDone,
62
+ testing: testingResponse.testing,
63
+ };
64
+ config.status_transitions = statusTransitions;
65
+ await saveConfig(config);
66
+ console.log("\nāœ… Status mappings updated:");
67
+ console.log(` Todo: ${statusTransitions.todo}`);
68
+ console.log(` In Progress: ${statusTransitions.in_progress}`);
69
+ console.log(` Done: ${statusTransitions.done}`);
70
+ if (statusTransitions.testing) {
71
+ console.log(` Testing: ${statusTransitions.testing}`);
72
+ }
73
+ }
@@ -0,0 +1,2 @@
1
+ import { type Config, type LocalConfig } from "../utils.js";
2
+ export declare function configureTeams(_config: Config, localConfig: LocalConfig): Promise<void>;
@@ -0,0 +1,59 @@
1
+ import prompts from "prompts";
2
+ import { getLinearClient, saveLocalConfig, } from "../utils.js";
3
+ export async function configureTeams(_config, localConfig) {
4
+ console.log("šŸ‘„ Configure Teams\n");
5
+ const client = getLinearClient();
6
+ const teamsData = await client.teams();
7
+ const teams = teamsData.nodes;
8
+ if (teams.length === 0) {
9
+ console.error("No teams found.");
10
+ process.exit(1);
11
+ }
12
+ // Multi-select teams
13
+ const teamsResponse = await prompts({
14
+ type: "multiselect",
15
+ name: "teamKeys",
16
+ message: "Select teams to sync (space to select):",
17
+ choices: teams.map((t) => {
18
+ const key = t.name.toLowerCase().replace(/[^a-z0-9]/g, "_");
19
+ const currentTeams = localConfig.teams || [localConfig.team];
20
+ return {
21
+ title: t.name,
22
+ value: key,
23
+ selected: currentTeams.includes(key),
24
+ };
25
+ }),
26
+ min: 1,
27
+ });
28
+ const selectedTeamKeys = teamsResponse.teamKeys || [localConfig.team];
29
+ // If multiple teams, ask for primary
30
+ let primaryTeam = localConfig.team;
31
+ if (selectedTeamKeys.length > 1) {
32
+ const primaryResponse = await prompts({
33
+ type: "select",
34
+ name: "primary",
35
+ message: "Select primary team:",
36
+ choices: selectedTeamKeys.map((key) => ({
37
+ title: key,
38
+ value: key,
39
+ })),
40
+ initial: selectedTeamKeys.indexOf(localConfig.team),
41
+ });
42
+ primaryTeam = primaryResponse.primary || selectedTeamKeys[0];
43
+ }
44
+ else {
45
+ primaryTeam = selectedTeamKeys[0];
46
+ }
47
+ localConfig.team = primaryTeam;
48
+ localConfig.teams =
49
+ selectedTeamKeys.length > 1 ? selectedTeamKeys : undefined;
50
+ await saveLocalConfig(localConfig);
51
+ console.log("\nāœ… Teams updated:");
52
+ if (localConfig.teams) {
53
+ console.log(` Selected: ${localConfig.teams.join(", ")}`);
54
+ console.log(` Primary: ${localConfig.team}`);
55
+ }
56
+ else {
57
+ console.log(` Team: ${localConfig.team}`);
58
+ }
59
+ }
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env bun
2
- import prompts from "prompts";
3
- import { getLinearClient, getTeamId, loadConfig, loadLocalConfig, saveConfig, saveLocalConfig, } from "./utils.js";
2
+ import { configureFilters } from "./config/filters.js";
3
+ import { showConfig } from "./config/show.js";
4
+ import { configureStatus } from "./config/status.js";
5
+ import { configureTeams } from "./config/teams.js";
6
+ import { loadConfig, loadLocalConfig } from "./utils.js";
4
7
  async function config() {
5
8
  const args = process.argv.slice(2);
6
- // Handle help flag
7
9
  if (args.includes("--help") || args.includes("-h")) {
8
10
  console.log(`Usage: ttt config [subcommand]
9
11
 
@@ -24,248 +26,22 @@ Examples:
24
26
  const subcommand = args[0] || "show";
25
27
  const configData = await loadConfig();
26
28
  const localConfig = await loadLocalConfig();
27
- if (subcommand === "show") {
28
- console.log("šŸ“‹ Current Configuration:\n");
29
- // Teams
30
- console.log("Teams:");
31
- if (localConfig.teams && localConfig.teams.length > 0) {
32
- console.log(` Selected: ${localConfig.teams.join(", ")}`);
33
- console.log(` Primary: ${localConfig.team}`);
34
- }
35
- else {
36
- console.log(` ${localConfig.team}`);
37
- }
38
- // User
39
- console.log(`\nUser: ${localConfig.current_user}`);
40
- // Filters
41
- console.log("\nFilters:");
42
- console.log(` Label: ${localConfig.label || "(all)"}`);
43
- console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
44
- console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
45
- // Status Mappings
46
- console.log("\nStatus Mappings:");
47
- if (configData.status_transitions) {
48
- const st = configData.status_transitions;
49
- console.log(` Todo: ${st.todo}`);
50
- console.log(` In Progress: ${st.in_progress}`);
51
- console.log(` Done: ${st.done}`);
52
- if (st.testing) {
53
- console.log(` Testing: ${st.testing}`);
54
- }
55
- }
56
- else {
57
- console.log(" (not configured)");
58
- }
59
- process.exit(0);
60
- }
61
- if (subcommand === "status") {
62
- console.log("šŸ“Š Configure Status Mappings\n");
63
- const client = getLinearClient();
64
- const teamId = getTeamId(configData, localConfig.team);
65
- // Fetch workflow states
66
- const statesData = await client.workflowStates({
67
- filter: { team: { id: { eq: teamId } } },
68
- });
69
- const states = statesData.nodes;
70
- if (states.length === 0) {
71
- console.error("No workflow states found for this team.");
72
- process.exit(1);
73
- }
74
- const stateChoices = states.map((s) => ({
75
- title: `${s.name} (${s.type})`,
76
- value: s.name,
77
- }));
78
- // Get current values or defaults
79
- const current = configData.status_transitions || {};
80
- const defaultTodo = current.todo ||
81
- states.find((s) => s.type === "unstarted")?.name ||
82
- "Todo";
83
- const defaultInProgress = current.in_progress ||
84
- states.find((s) => s.type === "started")?.name ||
85
- "In Progress";
86
- const defaultDone = current.done ||
87
- states.find((s) => s.type === "completed")?.name ||
88
- "Done";
89
- const defaultTesting = current.testing || states.find((s) => s.name === "Testing")?.name;
90
- const todoResponse = await prompts({
91
- type: "select",
92
- name: "todo",
93
- message: 'Select status for "Todo" (pending tasks):',
94
- choices: stateChoices,
95
- initial: stateChoices.findIndex((c) => c.value === defaultTodo),
96
- });
97
- const inProgressResponse = await prompts({
98
- type: "select",
99
- name: "in_progress",
100
- message: 'Select status for "In Progress" (working tasks):',
101
- choices: stateChoices,
102
- initial: stateChoices.findIndex((c) => c.value === defaultInProgress),
103
- });
104
- const doneResponse = await prompts({
105
- type: "select",
106
- name: "done",
107
- message: 'Select status for "Done" (completed tasks):',
108
- choices: stateChoices,
109
- initial: stateChoices.findIndex((c) => c.value === defaultDone),
110
- });
111
- // Testing is optional
112
- const testingChoices = [
113
- { title: "(None)", value: undefined },
114
- ...stateChoices,
115
- ];
116
- const testingResponse = await prompts({
117
- type: "select",
118
- name: "testing",
119
- message: 'Select status for "Testing" (optional, for parent tasks):',
120
- choices: testingChoices,
121
- initial: defaultTesting
122
- ? testingChoices.findIndex((c) => c.value === defaultTesting)
123
- : 0,
124
- });
125
- const statusTransitions = {
126
- todo: todoResponse.todo || defaultTodo,
127
- in_progress: inProgressResponse.in_progress || defaultInProgress,
128
- done: doneResponse.done || defaultDone,
129
- testing: testingResponse.testing,
130
- };
131
- configData.status_transitions = statusTransitions;
132
- await saveConfig(configData);
133
- console.log("\nāœ… Status mappings updated:");
134
- console.log(` Todo: ${statusTransitions.todo}`);
135
- console.log(` In Progress: ${statusTransitions.in_progress}`);
136
- console.log(` Done: ${statusTransitions.done}`);
137
- if (statusTransitions.testing) {
138
- console.log(` Testing: ${statusTransitions.testing}`);
139
- }
140
- }
141
- if (subcommand === "filters") {
142
- console.log("šŸ” Configure Filters\n");
143
- const client = getLinearClient();
144
- const teamId = getTeamId(configData, localConfig.team);
145
- // Fetch labels
146
- const labelsData = await client.issueLabels({
147
- filter: { team: { id: { eq: teamId } } },
148
- });
149
- const labels = labelsData.nodes;
150
- // Fetch users
151
- const team = await client.team(teamId);
152
- const members = await team.members();
153
- const users = members.nodes;
154
- // Label filter (optional)
155
- const labelChoices = [
156
- { title: "(No filter - sync all labels)", value: "" },
157
- ...labels.map((l) => ({ title: l.name, value: l.name })),
158
- ];
159
- const labelResponse = await prompts({
160
- type: "select",
161
- name: "label",
162
- message: "Select label filter (optional):",
163
- choices: labelChoices,
164
- initial: localConfig.label
165
- ? labelChoices.findIndex((c) => c.value === localConfig.label)
166
- : 0,
167
- });
168
- // Exclude labels
169
- const excludeLabelsResponse = await prompts({
170
- type: "multiselect",
171
- name: "excludeLabels",
172
- message: "Select labels to exclude (space to select):",
173
- choices: labels.map((l) => ({
174
- title: l.name,
175
- value: l.name,
176
- selected: localConfig.exclude_labels?.includes(l.name),
177
- })),
178
- });
179
- // Exclude users
180
- const excludeUsersResponse = await prompts({
181
- type: "multiselect",
182
- name: "excludeUsers",
183
- message: "Select users to exclude (space to select):",
184
- choices: users.map((u) => {
185
- const key = (u.displayName ||
186
- u.name ||
187
- u.email?.split("@")[0] ||
188
- "user")
189
- .toLowerCase()
190
- .replace(/[^a-z0-9]/g, "_");
191
- return {
192
- title: `${u.displayName || u.name} (${u.email})`,
193
- value: key,
194
- selected: localConfig.exclude_assignees?.includes(key),
195
- };
196
- }),
197
- });
198
- localConfig.label = labelResponse.label || undefined;
199
- localConfig.exclude_labels =
200
- excludeLabelsResponse.excludeLabels?.length > 0
201
- ? excludeLabelsResponse.excludeLabels
202
- : undefined;
203
- localConfig.exclude_assignees =
204
- excludeUsersResponse.excludeUsers?.length > 0
205
- ? excludeUsersResponse.excludeUsers
206
- : undefined;
207
- await saveLocalConfig(localConfig);
208
- console.log("\nāœ… Filters updated:");
209
- console.log(` Label: ${localConfig.label || "(all)"}`);
210
- console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
211
- console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
212
- }
213
- if (subcommand === "teams") {
214
- console.log("šŸ‘„ Configure Teams\n");
215
- const client = getLinearClient();
216
- const teamsData = await client.teams();
217
- const teams = teamsData.nodes;
218
- if (teams.length === 0) {
219
- console.error("No teams found.");
29
+ switch (subcommand) {
30
+ case "show":
31
+ showConfig(configData, localConfig);
32
+ break;
33
+ case "status":
34
+ await configureStatus(configData, localConfig);
35
+ break;
36
+ case "filters":
37
+ await configureFilters(configData, localConfig);
38
+ break;
39
+ case "teams":
40
+ await configureTeams(configData, localConfig);
41
+ break;
42
+ default:
43
+ console.error(`Unknown subcommand: ${subcommand}`);
220
44
  process.exit(1);
221
- }
222
- // Multi-select teams
223
- const teamsResponse = await prompts({
224
- type: "multiselect",
225
- name: "teamKeys",
226
- message: "Select teams to sync (space to select):",
227
- choices: teams.map((t) => {
228
- const key = t.name.toLowerCase().replace(/[^a-z0-9]/g, "_");
229
- const currentTeams = localConfig.teams || [localConfig.team];
230
- return {
231
- title: t.name,
232
- value: key,
233
- selected: currentTeams.includes(key),
234
- };
235
- }),
236
- min: 1,
237
- });
238
- const selectedTeamKeys = teamsResponse.teamKeys || [localConfig.team];
239
- // If multiple teams, ask for primary
240
- let primaryTeam = localConfig.team;
241
- if (selectedTeamKeys.length > 1) {
242
- const primaryResponse = await prompts({
243
- type: "select",
244
- name: "primary",
245
- message: "Select primary team:",
246
- choices: selectedTeamKeys.map((key) => ({
247
- title: key,
248
- value: key,
249
- })),
250
- initial: selectedTeamKeys.indexOf(localConfig.team),
251
- });
252
- primaryTeam = primaryResponse.primary || selectedTeamKeys[0];
253
- }
254
- else {
255
- primaryTeam = selectedTeamKeys[0];
256
- }
257
- localConfig.team = primaryTeam;
258
- localConfig.teams =
259
- selectedTeamKeys.length > 1 ? selectedTeamKeys : undefined;
260
- await saveLocalConfig(localConfig);
261
- console.log("\nāœ… Teams updated:");
262
- if (localConfig.teams) {
263
- console.log(` Selected: ${localConfig.teams.join(", ")}`);
264
- console.log(` Primary: ${localConfig.team}`);
265
- }
266
- else {
267
- console.log(` Team: ${localConfig.team}`);
268
- }
269
45
  }
270
46
  }
271
47
  config().catch(console.error);