@shortcut-cli/shortcut-cli 4.0.0 → 5.0.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.
Files changed (48) hide show
  1. package/README.md +428 -8
  2. package/build/bin/{short-api.cjs → short-api.js} +13 -16
  3. package/build/bin/short-create.js +76 -0
  4. package/build/bin/short-custom-field.js +50 -0
  5. package/build/bin/short-custom-fields.js +29 -0
  6. package/build/bin/{short-doc.cjs → short-doc.js} +34 -36
  7. package/build/bin/{short-docs.cjs → short-docs.js} +23 -15
  8. package/build/bin/short-epic.js +186 -0
  9. package/build/bin/short-epics.js +36 -0
  10. package/build/bin/short-find.js +6 -0
  11. package/build/bin/short-install.js +87 -0
  12. package/build/bin/{short-iteration.cjs → short-iteration.js} +41 -45
  13. package/build/bin/{short-iterations.cjs → short-iterations.js} +15 -19
  14. package/build/bin/short-label.js +130 -0
  15. package/build/bin/short-labels.js +27 -0
  16. package/build/bin/short-members.js +31 -0
  17. package/build/bin/short-objective.js +151 -0
  18. package/build/bin/short-objectives.js +63 -0
  19. package/build/bin/short-projects.js +31 -0
  20. package/build/bin/short-search.js +45 -0
  21. package/build/bin/short-story.js +458 -0
  22. package/build/bin/short-team.js +78 -0
  23. package/build/bin/short-teams.js +28 -0
  24. package/build/bin/short-workflows.js +29 -0
  25. package/build/bin/short-workspace.js +63 -0
  26. package/build/bin/short.js +8 -0
  27. package/build/lib/client.js +9 -0
  28. package/build/lib/{configure.cjs → configure.js} +18 -27
  29. package/build/lib/spinner.js +12 -0
  30. package/build/lib/{stories.cjs → stories.js} +116 -78
  31. package/build/package.js +5 -0
  32. package/package.json +44 -44
  33. package/build/_virtual/rolldown_runtime.cjs +0 -29
  34. package/build/bin/short-create.cjs +0 -58
  35. package/build/bin/short-epic.cjs +0 -74
  36. package/build/bin/short-epics.cjs +0 -36
  37. package/build/bin/short-find.cjs +0 -7
  38. package/build/bin/short-install.cjs +0 -42
  39. package/build/bin/short-members.cjs +0 -34
  40. package/build/bin/short-projects.cjs +0 -34
  41. package/build/bin/short-search.cjs +0 -49
  42. package/build/bin/short-story.cjs +0 -213
  43. package/build/bin/short-workflows.cjs +0 -32
  44. package/build/bin/short-workspace.cjs +0 -64
  45. package/build/bin/short.cjs +0 -10
  46. package/build/lib/client.cjs +0 -11
  47. package/build/lib/spinner.cjs +0 -17
  48. package/build/package.cjs +0 -18
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from "../lib/configure.js";
3
+ import client from "../lib/client.js";
4
+ import spinner from "../lib/spinner.js";
5
+ import stories_default from "../lib/stories.js";
6
+ import { Command } from "commander";
7
+ import debugging from "debug";
8
+ import fs from "fs";
9
+ import os from "os";
10
+ import path from "path";
11
+ import { execSync } from "child_process";
12
+ import chalk from "chalk";
13
+ import https from "https";
14
+ //#region src/bin/short-story.ts
15
+ const config = loadConfig();
16
+ const spin = spinner();
17
+ const log = console.log;
18
+ const logError = console.error;
19
+ const debug = debugging("short");
20
+ let handledSubcommand = false;
21
+ if (process.argv[2] === "history") {
22
+ handledSubcommand = true;
23
+ showStoryHistory(process.argv[3]).catch((e) => {
24
+ logError("Error fetching story history", e);
25
+ process.exit(1);
26
+ });
27
+ }
28
+ if (process.argv[2] === "comments") {
29
+ handledSubcommand = true;
30
+ showStoryComments(process.argv[3]).catch((e) => {
31
+ logError("Error fetching story comments", e);
32
+ process.exit(1);
33
+ });
34
+ }
35
+ if (process.argv[2] === "tasks") {
36
+ handledSubcommand = true;
37
+ showStoryTasks(process.argv[3]).catch((e) => {
38
+ logError("Error fetching story tasks", e);
39
+ process.exit(1);
40
+ });
41
+ }
42
+ if (process.argv[2] === "sub-tasks") {
43
+ handledSubcommand = true;
44
+ showStorySubTasks(process.argv[3]).catch((e) => {
45
+ logError("Error fetching story sub-tasks", e);
46
+ process.exit(1);
47
+ });
48
+ }
49
+ if (process.argv[2] === "relation" && process.argv[3] === "add") {
50
+ handledSubcommand = true;
51
+ addStoryRelation(process.argv[4], process.argv[5], process.argv[6], process.argv[7]).catch((e) => {
52
+ logError("Error creating story relation", e);
53
+ process.exit(1);
54
+ });
55
+ }
56
+ const program = new Command().allowExcessArguments(true).argument("[id]").usage("[options] <id>").description("Update and/or display story details").option("-a, --archived", "Update story as archived").option("-c, --comment [text]", "Add comment to story", "").option("-d, --description [text]", "Update description of story", "").option("--deadline [date]", "Update due date of story (YYYY-MM-DD)", "").option("-D, --download", "Download all attached files", "").option("--download-dir [path]", "Directory to download files to", ".").option("-e, --estimate [number]", "Update estimate of story", "").option("--epic [id|name]", "Set epic of story").option("-i, --iteration [id|name]", "Set iteration of story").option("-f, --format [template]", "Format the story output by template", "").option("--from-git", "Fetch story parsed by ID from current git branch").option("--follower [id|name]", "Update followers of story, comma-separated", "").option("--git-branch", "Checkout git branch from story slug <mention-name>/ch<id>/<type>-<title>\n as required by the Git integration: https://bit.ly/2RKO1FF").option("--git-branch-short", "Checkout git branch from story slug <mention-name>/ch<id>/<title>").option("-I, --idonly", "Print only ID of story results", "").option("-l, --label [id|name]", "Stories with label id/name, by regex", "").option("--move-after [id]", "Move story to position below story ID").option("--move-before [id]", "Move story to position above story ID").option("--move-down [n]", "Move story position downward by n stories").option("--move-up [n]", "Move story position upward by n stories").option("-o, --owners [id|name]", "Update owners of story, comma-separated", "").option("-O, --open", "Open story in browser").option("--oe, --open-epic", "Open story's epic in browser").option("--oi, --open-iteration", "Open story's iteration in browser").option("--op, --open-project", "Open story's project in browser").option("--external-link [url]", "Add external link to story, comma-separated", "").option("-q, --quiet", "Print only story output, no loading dialog", "").option("--requester [id|name]", "Update requester of story", "").option("-s, --state [id|name]", "Update workflow state of story", "").option("-t, --title [text]", "Update title/name of story", "").option("-T, --team [id|name]", "Update team/group of story", "").option("--task [text]", "Create new task on story").option("--task-complete [text]", "Toggle completion of task on story matching text").option("-y, --type [name]", "Update type of story", "").parse(process.argv);
57
+ const opts = program.opts();
58
+ const main = async () => {
59
+ if (handledSubcommand) return;
60
+ const entities = await stories_default.fetchEntities();
61
+ if (!(opts.idonly || opts.quiet)) spin.start();
62
+ debug("constructing story update");
63
+ const update = {};
64
+ if (opts.archived) update.archived = true;
65
+ if (opts.state) update.workflow_state_id = stories_default.findState(entities, opts.state)?.id;
66
+ if (opts.estimate) update.estimate = parseInt(opts.estimate, 10);
67
+ if (opts.title) update.name = opts.title;
68
+ if (opts.description) update.description = `${opts.description}`;
69
+ if (opts.deadline) update.deadline = normalizeDate(opts.deadline);
70
+ if (opts.type) {
71
+ const storyTypes = [
72
+ "feature",
73
+ "bug",
74
+ "chore"
75
+ ];
76
+ const typeMatch = new RegExp(opts.type, "i");
77
+ update.story_type = storyTypes.find((t) => t.match(typeMatch));
78
+ }
79
+ if (opts.owners) update.owner_ids = stories_default.findOwnerIds(entities, opts.owners);
80
+ if (opts.follower) update.follower_ids = stories_default.findOwnerIds(entities, opts.follower);
81
+ if (opts.epic) update.epic_id = stories_default.findEpic(entities, opts.epic)?.id;
82
+ if (opts.iteration) update.iteration_id = stories_default.findIteration(entities, opts.iteration)?.id;
83
+ if (opts.label) update.labels = stories_default.findLabelNames(entities, opts.label);
84
+ if (opts.team) update.group_id = stories_default.findGroup(entities, opts.team)?.id;
85
+ if (opts.requester) update.requested_by_id = stories_default.findMember(entities, opts.requester)?.id;
86
+ const hasPositionUpdate = opts.moveAfter !== void 0 || opts.moveBefore !== void 0 || opts.moveDown !== void 0 || opts.moveUp !== void 0;
87
+ const hasUpdate = Object.keys(update).length > 0 || hasPositionUpdate || !!opts.externalLink;
88
+ debug("constructed story update", update);
89
+ const gitID = [];
90
+ if (opts.fromGit || !program.args.length) {
91
+ debug("fetching story ID from git");
92
+ let branch = "";
93
+ try {
94
+ branch = execSync("git branch").toString("utf-8");
95
+ } catch (e) {
96
+ debug(e);
97
+ }
98
+ const storyIdMatch = branch.match(/\*.*/)?.[0].match(/\/(ch|sc-)([0-9]+)/);
99
+ if (storyIdMatch?.[2]) {
100
+ debug("parsing story ID from git branch:", branch);
101
+ const id = parseInt(storyIdMatch[2], 10);
102
+ debug("parsed story ID from git branch:", id);
103
+ if (id) gitID.push(id.toString());
104
+ } else {
105
+ stopSpinner();
106
+ logError("No story ID argument present or found in git branch");
107
+ process.exit(2);
108
+ }
109
+ }
110
+ program.args.map((a) => (a.match(/\d+/) || [])[0]).concat(gitID).map(async (_id) => {
111
+ if (!_id) return;
112
+ const id = parseInt(_id, 10);
113
+ let story;
114
+ try {
115
+ if (opts.comment) {
116
+ debug("request comment create");
117
+ await client.createStoryComment(id, { text: opts.comment });
118
+ debug("response comment create");
119
+ }
120
+ } catch (e) {
121
+ stopSpinner();
122
+ log("Error creating comment", id);
123
+ process.exit(3);
124
+ }
125
+ try {
126
+ if (opts.task) {
127
+ debug("request task create");
128
+ await client.createTask(id, { description: opts.task });
129
+ debug("response task create");
130
+ }
131
+ } catch (e) {
132
+ stopSpinner();
133
+ log("Error creating task", id);
134
+ process.exit(3);
135
+ }
136
+ try {
137
+ debug("request story");
138
+ story = await client.getStory(id).then((r) => r.data);
139
+ debug("response story");
140
+ if (opts.externalLink) {
141
+ const links = opts.externalLink.split(",").map((link) => link.trim()).filter(Boolean);
142
+ update.external_links = Array.from(new Set([...story.external_links || [], ...links]));
143
+ }
144
+ } catch (e) {
145
+ stopSpinner();
146
+ logError("Error fetching story", id);
147
+ process.exit(4);
148
+ }
149
+ try {
150
+ if (opts.taskComplete) {
151
+ debug("calculating task(s) to complete");
152
+ const descMatch = new RegExp(opts.taskComplete, "i");
153
+ const tasks = story.tasks.filter((t) => t.description.match(descMatch));
154
+ const updatedTaskIds = tasks.map((t) => t.id);
155
+ debug("request tasks complete", updatedTaskIds);
156
+ await Promise.all(tasks.map((t) => client.updateTask(id, t.id, { complete: !t.complete })));
157
+ debug("response tasks complete");
158
+ story.tasks = story.tasks.map((t) => {
159
+ if (updatedTaskIds.indexOf(t.id) > -1) t.complete = !t.complete;
160
+ return t;
161
+ });
162
+ }
163
+ } catch (e) {
164
+ stopSpinner();
165
+ log("Error updating tasks", e);
166
+ process.exit(3);
167
+ }
168
+ try {
169
+ if (hasUpdate) {
170
+ if (hasPositionUpdate) {
171
+ debug("calculating move up/down");
172
+ const siblings = await stories_default.listStories({
173
+ state: story.workflow_state_id.toString(),
174
+ sort: "state.position:asc,position:asc"
175
+ });
176
+ const siblingIds = siblings.map((s) => s.id);
177
+ const storyIndex = siblingIds.indexOf(~~id);
178
+ if (opts.moveAfter) update.after_id = ~~opts.moveAfter;
179
+ else if (opts.moveBefore) update.before_id = ~~opts.moveBefore;
180
+ else if (opts.moveUp) update.before_id = siblingIds[Math.max(0, storyIndex - (~~opts.moveUp || 1))];
181
+ else if (opts.moveDown) update.after_id = siblingIds[Math.min(siblings.length - 1, storyIndex + (~~opts.moveDown || 1))];
182
+ debug("constructed story position update", update);
183
+ }
184
+ debug("request story update");
185
+ const changed = await client.updateStory(id, update).then((r) => r.data);
186
+ debug("response story update");
187
+ story = Object.assign({}, story, changed);
188
+ }
189
+ } catch (e) {
190
+ stopSpinner();
191
+ logError("Error updating story", id);
192
+ process.exit(5);
193
+ }
194
+ if (story) story = stories_default.hydrateStory(entities, story);
195
+ if (!opts.idonly) spin.stop(true);
196
+ if (story) {
197
+ printStory(story, entities);
198
+ if (opts.open) openURL(stories_default.storyURL(story));
199
+ if (opts.openEpic) {
200
+ if (!story.epic_id) {
201
+ logError("This story is not part of an epic.");
202
+ process.exit(21);
203
+ }
204
+ openURL(stories_default.buildURL("epic", story.epic_id));
205
+ }
206
+ if (opts.openIteration) {
207
+ if (!story.iteration_id) {
208
+ logError("This story is not part of an iteration.");
209
+ process.exit(22);
210
+ }
211
+ openURL(stories_default.buildURL("iteration", story.iteration_id));
212
+ }
213
+ if (opts.openProject) {
214
+ if (story.project_id !== void 0 && story.project_id !== null) openURL(stories_default.buildURL("project", story.project_id));
215
+ }
216
+ }
217
+ if (opts.download) downloadFiles(story);
218
+ if (story && opts.gitBranch) {
219
+ if (!config.mentionName) {
220
+ stopSpinner();
221
+ stories_default.checkoutStoryBranch(story, `${story.story_type}-${story.id}-`);
222
+ logError("Error creating story branch in Shortcut format");
223
+ logError("Please run: \"short install --force\" to add your mention name to the config.");
224
+ process.exit(10);
225
+ }
226
+ stories_default.checkoutStoryBranch(story);
227
+ } else if (story && opts.gitBranchShort) stories_default.checkoutStoryBranch(story, `${config.mentionName}/sc-${story.id}/`);
228
+ });
229
+ stopSpinner();
230
+ };
231
+ async function showStoryHistory(idArg) {
232
+ const id = parseInt(idArg || "", 10);
233
+ if (!id) {
234
+ logError("Usage: short story history <id>");
235
+ process.exit(2);
236
+ }
237
+ spin.start();
238
+ try {
239
+ const history = await client.storyHistory(id).then((r) => r.data);
240
+ spin.stop(true);
241
+ if (history.length === 0) {
242
+ log(`No history found for story #${id}`);
243
+ process.exit(0);
244
+ }
245
+ history.forEach(printHistoryItem);
246
+ process.exit(0);
247
+ } catch (e) {
248
+ spin.stop(true);
249
+ logError(`Error fetching story history ${id}`);
250
+ process.exit(4);
251
+ }
252
+ }
253
+ async function showStoryComments(idArg) {
254
+ const id = parseInt(idArg || "", 10);
255
+ if (!id) {
256
+ logError("Usage: short story comments <id>");
257
+ process.exit(2);
258
+ }
259
+ spin.start();
260
+ try {
261
+ const entities = await stories_default.fetchEntities();
262
+ const story = await client.getStory(id).then((r) => r.data);
263
+ spin.stop(true);
264
+ if (!story.comments.length) {
265
+ log(`No comments found for story #${id}`);
266
+ process.exit(0);
267
+ }
268
+ printStoryComments(story.comments, entities);
269
+ process.exit(0);
270
+ } catch (e) {
271
+ spin.stop(true);
272
+ logError(`Error fetching story comments ${id}`);
273
+ process.exit(4);
274
+ }
275
+ }
276
+ async function showStoryTasks(idArg) {
277
+ const id = parseInt(idArg || "", 10);
278
+ if (!id) {
279
+ logError("Usage: short story tasks <id>");
280
+ process.exit(2);
281
+ }
282
+ spin.start();
283
+ try {
284
+ const entities = await stories_default.fetchEntities();
285
+ const story = await client.getStory(id).then((r) => r.data);
286
+ spin.stop(true);
287
+ if (!story.tasks.length) {
288
+ log(`No tasks found for story #${id}`);
289
+ process.exit(0);
290
+ }
291
+ printStoryTasks(story.tasks, entities);
292
+ process.exit(0);
293
+ } catch (e) {
294
+ spin.stop(true);
295
+ logError(`Error fetching story tasks ${id}`);
296
+ process.exit(4);
297
+ }
298
+ }
299
+ async function showStorySubTasks(idArg) {
300
+ const id = parseInt(idArg || "", 10);
301
+ if (!id) {
302
+ logError("Usage: short story sub-tasks <id>");
303
+ process.exit(2);
304
+ }
305
+ spin.start();
306
+ try {
307
+ const entities = await stories_default.fetchEntities();
308
+ const stories = await client.listStorySubTasks(id).then((r) => r.data);
309
+ spin.stop(true);
310
+ if (!stories.length) {
311
+ log(`No sub-tasks found for story #${id}`);
312
+ process.exit(0);
313
+ }
314
+ stories.map((story) => stories_default.hydrateStory(entities, story)).forEach(stories_default.printFormattedStory({}));
315
+ process.exit(0);
316
+ } catch (e) {
317
+ spin.stop(true);
318
+ logError(`Error fetching story sub-tasks ${id}`);
319
+ process.exit(4);
320
+ }
321
+ }
322
+ async function addStoryRelation(storyIdArg, relatedIdArg, typeFlag, typeValue) {
323
+ const storyId = parseInt(storyIdArg || "", 10);
324
+ const relatedId = parseInt(relatedIdArg || "", 10);
325
+ const relationType = typeFlag === "--type" ? typeValue : void 0;
326
+ if (!storyId || !relatedId || !relationType) {
327
+ logError("Usage: short story relation add <storyId> <relatedId> --type <type>");
328
+ process.exit(2);
329
+ }
330
+ const normalized = normalizeRelationType(storyId, relatedId, relationType);
331
+ if (!normalized) {
332
+ logError("Invalid relation type. Use one of: blocks, blocked-by, duplicates, duplicated-by, relates-to");
333
+ process.exit(2);
334
+ }
335
+ spin.start();
336
+ try {
337
+ const link = await client.createStoryLink(normalized).then((r) => r.data);
338
+ spin.stop(true);
339
+ log(`Added relation: story #${link.subject_id} ${link.verb} story #${link.object_id} (#${link.id})`);
340
+ } catch (e) {
341
+ spin.stop(true);
342
+ logError(`Error creating story relation ${storyId} -> ${relatedId}`);
343
+ process.exit(4);
344
+ }
345
+ }
346
+ const openURL = (url) => {
347
+ execSync(`${os.platform() === "darwin" ? "open" : "xdg-open"} '${url}'`);
348
+ };
349
+ const stopSpinner = () => {
350
+ if (!(opts.idonly || opts.quiet)) spin.stop(true);
351
+ };
352
+ const downloadFiles = (story) => story.files.map((file) => {
353
+ https.get(stories_default.fileURL(file), (res) => {
354
+ const filePath = path.join(opts.downloadDir ?? ".", file.name);
355
+ log(chalk.bold("Downloading file to: ") + filePath);
356
+ const stream = fs.createWriteStream(filePath);
357
+ res.pipe(stream);
358
+ stream.on("finish", () => stream.close());
359
+ });
360
+ });
361
+ const printStory = (story, entities) => {
362
+ if (opts.idonly) return log(story.id);
363
+ if (opts.format) return stories_default.printFormattedStory(opts)(story);
364
+ stories_default.printDetailedStory(story, entities);
365
+ };
366
+ const printHistoryItem = (item) => {
367
+ const actor = item.actor_name || item.member_id || "Unknown";
368
+ log(chalk.blue.bold(`${item.changed_at}`) + ` ${actor}`);
369
+ item.actions.forEach((action) => {
370
+ log(`- ${summarizeHistoryAction(action)}`);
371
+ });
372
+ log();
373
+ };
374
+ const printStoryComments = (comments, entities) => {
375
+ const repliesByParent = /* @__PURE__ */ new Map();
376
+ const roots = [];
377
+ comments.forEach((comment) => {
378
+ if (comment.parent_id) {
379
+ const replies = repliesByParent.get(comment.parent_id) || [];
380
+ replies.push(comment);
381
+ repliesByParent.set(comment.parent_id, replies);
382
+ } else roots.push(comment);
383
+ });
384
+ roots.sort((a, b) => a.position - b.position).forEach((comment) => printStoryComment(comment, entities, repliesByParent, 0));
385
+ };
386
+ const printStoryComment = (comment, entities, repliesByParent, depth) => {
387
+ const indent = " ".repeat(depth);
388
+ const author = comment.author_id ? entities.membersById?.get(comment.author_id)?.profile : void 0;
389
+ const authorText = author ? `${author.name} (${author.mention_name})` : comment.author_id || "Unknown";
390
+ log(`${indent}${chalk.bold("#" + comment.id)} ${authorText}`);
391
+ log(`${indent}Created: ${comment.created_at}`);
392
+ if (comment.updated_at && comment.updated_at !== comment.created_at) log(`${indent}Updated: ${comment.updated_at}`);
393
+ if (comment.blocker) log(`${indent}Blocker: true`);
394
+ log(`${indent}${comment.deleted ? "[deleted]" : comment.text || "_"}`);
395
+ log(`${indent}URL: ${comment.app_url}`);
396
+ log();
397
+ (repliesByParent.get(comment.id) || []).sort((a, b) => a.position - b.position).forEach((reply) => printStoryComment(reply, entities, repliesByParent, depth + 1));
398
+ };
399
+ const printStoryTasks = (tasks, entities) => {
400
+ tasks.slice().sort((a, b) => a.position - b.position).forEach((task) => printStoryTask(task, entities));
401
+ };
402
+ const printStoryTask = (task, entities) => {
403
+ const status = task.complete ? "[x]" : "[ ]";
404
+ const owners = task.owner_ids.map((ownerId) => entities.membersById?.get(ownerId)?.profile?.mention_name || ownerId).join(", ");
405
+ log(`${chalk.bold("#" + task.id)} ${status} ${task.description}`);
406
+ if (owners) log(`Owners: ${owners}`);
407
+ if (task.completed_at) log(`Completed: ${task.completed_at}`);
408
+ if (task.updated_at) log(`Updated: ${task.updated_at}`);
409
+ log();
410
+ };
411
+ function normalizeRelationType(storyId, relatedId, type) {
412
+ const normalized = type.toLowerCase().replace(/[_\s]+/g, "-");
413
+ if (normalized === "blocks") return {
414
+ subject_id: storyId,
415
+ object_id: relatedId,
416
+ verb: "blocks"
417
+ };
418
+ if (normalized === "blocked-by") return {
419
+ subject_id: relatedId,
420
+ object_id: storyId,
421
+ verb: "blocks"
422
+ };
423
+ if (normalized === "duplicates") return {
424
+ subject_id: storyId,
425
+ object_id: relatedId,
426
+ verb: "duplicates"
427
+ };
428
+ if (normalized === "duplicated-by") return {
429
+ subject_id: relatedId,
430
+ object_id: storyId,
431
+ verb: "duplicates"
432
+ };
433
+ if (normalized === "relates-to" || normalized === "relates") return {
434
+ subject_id: storyId,
435
+ object_id: relatedId,
436
+ verb: "relates to"
437
+ };
438
+ }
439
+ const summarizeHistoryAction = (action) => {
440
+ const entityType = String(action.entity_type || "item");
441
+ const actionType = String(action.action || "changed");
442
+ const name = typeof action.name === "string" ? action.name : void 0;
443
+ const description = typeof action.description === "string" ? action.description : void 0;
444
+ if (actionType === "update" && action.changes && typeof action.changes === "object") {
445
+ const fields = Object.keys(action.changes);
446
+ if (fields.length > 0) return `updated ${entityType} ${name ? `"${name}" ` : ""}(fields: ${fields.join(", ")})`;
447
+ }
448
+ if (name) return `${actionType} ${entityType} "${name}"`;
449
+ if (description) return `${actionType} ${entityType} "${description}"`;
450
+ return `${actionType} ${entityType}`;
451
+ };
452
+ const normalizeDate = (value) => {
453
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return (/* @__PURE__ */ new Date(`${value}T00:00:00.000Z`)).toISOString();
454
+ return new Date(value).toISOString();
455
+ };
456
+ main();
457
+ //#endregion
458
+ export {};
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import client from "../lib/client.js";
3
+ import spinner from "../lib/spinner.js";
4
+ import stories_default from "../lib/stories.js";
5
+ import { Command } from "commander";
6
+ import chalk from "chalk";
7
+ //#region src/bin/short-team.ts
8
+ const spin = spinner();
9
+ const log = console.log;
10
+ const program = new Command().usage("[command] [options]").description("view a team or list its stories");
11
+ program.command("view <idOrName>").description("view a team by id or name").action(viewTeam);
12
+ program.command("stories <idOrName>").description("list stories for a team by id or name").option("-d, --detailed", "Show more details for each story").option("-f, --format [template]", "Format each story output by template", "").action(listTeamStories);
13
+ const args = process.argv.slice(2);
14
+ if (args.length > 0 && args[0] !== "view" && args[0] !== "stories") process.argv.splice(2, 0, "view");
15
+ program.parse(process.argv);
16
+ if (args.length === 0) {
17
+ program.outputHelp();
18
+ process.exit(1);
19
+ }
20
+ async function viewTeam(idOrName) {
21
+ spin.start();
22
+ try {
23
+ const entities = await stories_default.fetchEntities();
24
+ const team = stories_default.findGroup(entities, idOrName);
25
+ if (!team) {
26
+ spin.stop(true);
27
+ log(`Team ${idOrName} not found`);
28
+ process.exit(1);
29
+ }
30
+ const fullTeam = await client.getGroup(team.id).then((r) => r.data);
31
+ spin.stop(true);
32
+ printTeam(fullTeam);
33
+ } catch (e) {
34
+ spin.stop(true);
35
+ log(`Error fetching team: ${e.message ?? String(e)}`);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ async function listTeamStories(idOrName, options) {
40
+ spin.start();
41
+ try {
42
+ const entities = await stories_default.fetchEntities();
43
+ const team = stories_default.findGroup(entities, idOrName);
44
+ if (!team) {
45
+ spin.stop(true);
46
+ log(`Team ${idOrName} not found`);
47
+ process.exit(1);
48
+ }
49
+ const stories = await client.listGroupStories(team.id).then((r) => r.data);
50
+ spin.stop(true);
51
+ if (stories.length === 0) {
52
+ log(`No stories found for team #${team.id} ${team.name}`);
53
+ return;
54
+ }
55
+ stories.map((story) => stories_default.hydrateStory(entities, story)).forEach(options.detailed ? (story) => stories_default.printDetailedStory(story, entities) : stories_default.printFormattedStory({ format: options.format }));
56
+ } catch (e) {
57
+ spin.stop(true);
58
+ log(`Error fetching team stories: ${e.message ?? String(e)}`);
59
+ process.exit(1);
60
+ }
61
+ }
62
+ function printTeam(team) {
63
+ log(chalk.bold(`#${team.id}`) + chalk.blue(` ${team.name}`));
64
+ log(chalk.bold("Mention: ") + ` ${team.mention_name}`);
65
+ log(chalk.bold("Stories: ") + ` ${team.num_stories}`);
66
+ log(chalk.bold("Started: ") + ` ${team.num_stories_started}`);
67
+ log(chalk.bold("Backlog: ") + ` ${team.num_stories_backlog}`);
68
+ log(chalk.bold("Epics Started: ") + ` ${team.num_epics_started}`);
69
+ log(chalk.bold("Members: ") + ` ${team.member_ids.length}`);
70
+ log(chalk.bold("Workflows: ") + ` ${team.workflow_ids.length}`);
71
+ log(chalk.bold("Archived: ") + ` ${team.archived}`);
72
+ if (team.description) log(chalk.bold("Description: ") + ` ${team.description}`);
73
+ if (team.color) log(chalk.bold("Color: ") + ` ${team.color}`);
74
+ log(chalk.bold("URL: ") + ` ${team.app_url}`);
75
+ log();
76
+ }
77
+ //#endregion
78
+ export {};
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import client from "../lib/client.js";
3
+ import spinner from "../lib/spinner.js";
4
+ import { Command } from "commander";
5
+ import chalk from "chalk";
6
+ //#region src/bin/short-teams.ts
7
+ const spin = spinner();
8
+ const log = console.log;
9
+ const opts = new Command().description("Display teams available for stories and epics").option("-a, --archived", "List teams including archived", "").option("-s, --search [query]", "List teams with name containing query", "").parse(process.argv).opts();
10
+ async function main() {
11
+ spin.start();
12
+ const teams = await client.listGroups().then((r) => r.data);
13
+ spin.stop(true);
14
+ const searchMatch = new RegExp(opts.search ?? "", "i");
15
+ teams.filter((team) => !!`${team.id} ${team.name} ${team.mention_name}`.match(searchMatch)).forEach(printTeamSummary);
16
+ }
17
+ function printTeamSummary(team) {
18
+ if (team.archived && !opts.archived) return;
19
+ log(chalk.bold(`#${team.id}`) + chalk.blue(` ${team.name}`));
20
+ log(chalk.bold("Mention: ") + ` ${team.mention_name}`);
21
+ log(chalk.bold("Stories: ") + ` ${team.num_stories}`);
22
+ log(chalk.bold("Started: ") + ` ${team.num_stories_started}`);
23
+ if (team.archived) log(chalk.bold("Archived: ") + ` ${team.archived}`);
24
+ log();
25
+ }
26
+ main();
27
+ //#endregion
28
+ export {};
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import client from "../lib/client.js";
3
+ import spinner from "../lib/spinner.js";
4
+ import { Command } from "commander";
5
+ import chalk from "chalk";
6
+ //#region src/bin/short-workflows.ts
7
+ const spin = spinner();
8
+ const log = console.log;
9
+ const opts = new Command().description("Display workflows/states available for stories").option("-s, --search [query]", "List states containing query", "").parse(process.argv).opts();
10
+ const main = async () => {
11
+ spin.start();
12
+ const wfs = await client.listWorkflows().then((r) => r.data);
13
+ spin.stop(true);
14
+ wfs.map(printWf);
15
+ };
16
+ const printWf = (wf) => {
17
+ log(chalk.bold(`#${wf.id}`) + ` ${wf.name}`);
18
+ log(" == States:");
19
+ wf.states.map(printWfState);
20
+ };
21
+ const printWfState = (state) => {
22
+ if (!state.name.match(new RegExp(opts.search ?? "", "i"))) return;
23
+ log(chalk.bold(` #${state.id}`) + ` ${state.name}`);
24
+ log(` Type: \t${state.type}`);
25
+ log(` Stories:\t${state.num_stories}`);
26
+ };
27
+ main();
28
+ //#endregion
29
+ export {};
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import configure_default from "../lib/configure.js";
3
+ import stories_default from "../lib/stories.js";
4
+ import { program as program$1 } from "./short-search.js";
5
+ import { Command } from "commander";
6
+ //#region src/bin/short-workspace.ts
7
+ const config = configure_default.loadConfig();
8
+ const log = console.log;
9
+ const program = new Command().description("List stories matching saved workspace query").argument("[name]").option("-l, --list", "List saved workspaces").option("-q, --quiet", "Print only workspace story output, no loading dialog", "").option("-n, --name [name]", "Load named workspace", "").option("-u, --unset [name]", "Force unset saved workspace").parse(process.argv);
10
+ const opts = program.opts();
11
+ const toArgs = (obj) => Object.entries(obj).map(([k, v]) => `--${k} '${v}'`).join(" ");
12
+ const main = async () => {
13
+ if (!config || !config.token) {
14
+ log("Not installed yet.");
15
+ log("Please run: short install");
16
+ return;
17
+ } else if (!config.workspaces) {
18
+ log("No workspace saved.");
19
+ log("Please run:");
20
+ log(" short search [options] --save");
21
+ log("to create your first one.");
22
+ return;
23
+ } else if (opts.list) {
24
+ log("Workspaces:");
25
+ const workspaces = config.workspaces ?? {};
26
+ Object.keys(workspaces).forEach((w) => {
27
+ log(" ", w + ":", toArgs(workspaces[w] ?? {}));
28
+ });
29
+ return;
30
+ } else if (opts.unset) {
31
+ if (configure_default.removeWorkspace(opts.unset)) log("Successfully removed %s workspace", opts.unset);
32
+ else log("Failed to remove %s workspace", opts.unset);
33
+ return;
34
+ }
35
+ const name = `${opts.name || program.args[0] || "default"}`;
36
+ const workspace = config.workspaces?.[name];
37
+ if (!workspace) {
38
+ log("No workspace saved with name", name);
39
+ log("Please run:");
40
+ log(" short search [options] --save", name);
41
+ log("to create it.");
42
+ return;
43
+ }
44
+ const foundOpts = program$1.parse(process.argv).opts();
45
+ const additionalArgs = {
46
+ ...workspace,
47
+ ...Object.fromEntries(Object.entries(foundOpts).filter(([, v]) => v !== void 0 && v !== "" && v !== false))
48
+ };
49
+ if (!opts.quiet) {
50
+ log("Loading %s workspace ...", name);
51
+ log();
52
+ }
53
+ let stories = [];
54
+ try {
55
+ stories = await stories_default.listStories(additionalArgs);
56
+ } catch (e) {
57
+ log("Error fetching stories:", e);
58
+ }
59
+ stories.map(stories_default.printFormattedStory(additionalArgs));
60
+ };
61
+ main();
62
+ //#endregion
63
+ export {};
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { description, version } from "../package.js";
3
+ import { Command } from "commander";
4
+ //#region src/bin/short.ts
5
+ process.on("unhandledRejection", console.log);
6
+ new Command().version(version).description(description).command("install [options]", "install and configure API access").command("search [options] [SEARCH OPERATORS]", "search stories with optional query").alias("s").command("find [options] [SEARCH OPERATORS]", "[DEPRECATED] search stories with optional query").command("story [ID|command] [options]", "view or manipulate stories").alias("st").command("create [options]", "create a story").alias("c").command("members [options]", "list members").alias("m").command("teams [options]", "list teams").command("team [command] [options]", "view a team or list its stories").command("labels [options]", "list labels").command("label [command] [options]", "view stories for a label").command("custom-fields [options]", "list custom fields").command("custom-field <id> [options]", "view a custom field").command("workflows [options]", "list workflows and their states").alias("wf").command("epics [options]", "list epics and their states").alias("e").command("epic [command] [options]", "create, view, or update an epic").command("objectives [options]", "list objectives and their states").alias("o").command("objective [command] [options]", "view, create, or update objectives").command("iterations [options]", "list iterations").alias("i").command("iteration [command] [options]", "view, create, update, or delete an iteration").command("docs [options]", "list and search docs").alias("d").command("doc [command] [options]", "view, create, or update a doc").command("projects [options]", "list projects and their states").alias("p").command("workspace [NAME] [options]", "list stories matching saved workspace query", { isDefault: true }).alias("w").command("api <path> [options]", "make a request to the Shortcut API").parse(process.argv);
7
+ //#endregion
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import { loadConfig } from "./configure.js";
2
+ import { ShortcutClient } from "@shortcut/client";
3
+ //#region src/lib/client.ts
4
+ const config = loadConfig();
5
+ const clientConfig = {};
6
+ if (process.env.SHORTCUT_API_BASE_URL) clientConfig.baseURL = process.env.SHORTCUT_API_BASE_URL;
7
+ const client = new ShortcutClient(config.token ?? "", clientConfig);
8
+ //#endregion
9
+ export { client as default };