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.
- package/README.md +59 -8
- package/README.zh-TW.md +111 -22
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +87 -61
- package/dist/scripts/config.d.ts +2 -0
- package/dist/scripts/config.js +271 -0
- package/dist/scripts/done-job.d.ts +1 -0
- package/dist/scripts/done-job.js +215 -186
- package/dist/scripts/init.d.ts +2 -0
- package/dist/scripts/init.js +457 -278
- package/dist/scripts/status.d.ts +2 -0
- package/dist/scripts/status.js +251 -0
- package/dist/scripts/sync.d.ts +1 -0
- package/dist/scripts/sync.js +228 -152
- package/dist/scripts/utils.d.ts +109 -0
- package/dist/scripts/utils.js +116 -14766
- package/dist/scripts/work-on.d.ts +1 -0
- package/dist/scripts/work-on.js +132 -122
- package/package.json +52 -48
- package/templates/claude-code-commands/done-job.md +45 -0
- package/templates/claude-code-commands/sync-linear.md +32 -0
- package/templates/claude-code-commands/work-on.md +41 -0
- package/dist/cli-6rkvcjaj.js +0 -4923
- package/dist/cli-pyanjjwn.js +0 -21
|
@@ -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 {};
|