team-toon-tack 1.0.11 → 1.6.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.
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env bun
2
+ import prompts from "prompts";
3
+ import { getLinearClient, getTeamId, loadConfig, loadLocalConfig, saveConfig, saveLocalConfig, } from "./utils.js";
4
+ async function config() {
5
+ const args = process.argv.slice(2);
6
+ // Handle help flag
7
+ if (args.includes("--help") || args.includes("-h")) {
8
+ console.log(`Usage: ttt config [subcommand]
9
+
10
+ Subcommands:
11
+ show Show current configuration
12
+ status Configure status mappings (todo, in_progress, done, testing)
13
+ filters Configure filters (label, exclude_labels, exclude_assignees)
14
+ teams Configure team selection
15
+
16
+ Examples:
17
+ ttt config # Show current config
18
+ ttt config show # Show current config
19
+ ttt config status # Configure status mappings
20
+ ttt config filters # Configure filter settings
21
+ ttt config teams # Configure team selection`);
22
+ process.exit(0);
23
+ }
24
+ const subcommand = args[0] || "show";
25
+ const configData = await loadConfig();
26
+ 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.");
220
+ 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
+ }
270
+ }
271
+ config().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};