@shortcut-cli/shortcut-cli 3.8.1 → 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.
@@ -1,71 +1,105 @@
1
1
  #!/usr/bin/env node
2
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js');
3
- const require_lib_spinner = require('../lib/spinner.js');
4
- const require_lib_configure = require('../lib/configure.js');
5
- const require_lib_client = require('../lib/client.js');
6
- const require_lib_stories = require('../lib/stories.js');
7
- let commander = require("commander");
8
- commander = require_rolldown_runtime.__toESM(commander);
9
- let debug = require("debug");
10
- debug = require_rolldown_runtime.__toESM(debug);
11
- let path = require("path");
12
- path = require_rolldown_runtime.__toESM(path);
13
- let fs = require("fs");
14
- fs = require_rolldown_runtime.__toESM(fs);
15
- let os = require("os");
16
- os = require_rolldown_runtime.__toESM(os);
17
- let child_process = require("child_process");
18
- let chalk = require("chalk");
19
- chalk = require_rolldown_runtime.__toESM(chalk);
20
- let https = require("https");
21
- https = require_rolldown_runtime.__toESM(https);
22
-
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";
23
14
  //#region src/bin/short-story.ts
24
- const config = require_lib_configure.loadConfig();
25
- const spin = require_lib_spinner.default();
15
+ const config = loadConfig();
16
+ const spin = spinner();
26
17
  const log = console.log;
27
18
  const logError = console.error;
28
- const debug$1 = (0, debug.default)("short");
29
- const program = commander.default.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("-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("--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("-q, --quiet", "Print only story output, no loading dialog", "").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);
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();
30
58
  const main = async () => {
31
- const entities = await require_lib_stories.default.fetchEntities();
32
- if (!(program.idonly || program.quiet)) spin.start();
33
- debug$1("constructing story update");
59
+ if (handledSubcommand) return;
60
+ const entities = await stories_default.fetchEntities();
61
+ if (!(opts.idonly || opts.quiet)) spin.start();
62
+ debug("constructing story update");
34
63
  const update = {};
35
- if (program.archived) update.archived = true;
36
- if (program.state) update.workflow_state_id = (require_lib_stories.default.findState(entities, program.state) || {}).id;
37
- if (program.estimate) update.estimate = parseInt(program.estimate, 10);
38
- if (program.title) update.name = program.title;
39
- if (program.description) update.description = `${program.description}`;
40
- if (program.type) {
41
- const typeMatch = new RegExp(program.type, "i");
42
- update.story_type = [
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 = [
43
72
  "feature",
44
73
  "bug",
45
74
  "chore"
46
- ].filter((t) => !!t.match(typeMatch))[0];
75
+ ];
76
+ const typeMatch = new RegExp(opts.type, "i");
77
+ update.story_type = storyTypes.find((t) => t.match(typeMatch));
47
78
  }
48
- if (program.owners) update.owner_ids = require_lib_stories.default.findOwnerIds(entities, program.owners);
49
- if (program.epic) update.epic_id = (require_lib_stories.default.findEpic(entities, program.epic) || {}).id;
50
- if (program.iteration) update.iteration_id = (require_lib_stories.default.findIteration(entities, program.iteration) || {}).id;
51
- if (program.label) update.labels = require_lib_stories.default.findLabelNames(entities, program.label);
52
- if (program.team) update.group_id = (require_lib_stories.default.findGroup(entities, program.team) || {}).id;
53
- const hasPositionUpdate = program.moveAfter !== void 0 || program.moveBefore !== void 0 || program.moveDown !== void 0 || program.moveUp !== void 0;
54
- const hasUpdate = Object.keys(update).length > 0 || hasPositionUpdate;
55
- debug$1("constructed story update", update);
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);
56
89
  const gitID = [];
57
- if (program.fromGit || !program.args.length) {
58
- debug$1("fetching story ID from git");
90
+ if (opts.fromGit || !program.args.length) {
91
+ debug("fetching story ID from git");
59
92
  let branch = "";
60
93
  try {
61
- branch = (0, child_process.execSync)("git branch").toString("utf-8");
94
+ branch = execSync("git branch").toString("utf-8");
62
95
  } catch (e) {
63
- debug$1(e);
96
+ debug(e);
64
97
  }
65
- if (branch.match(/\*.*[0-9]+/)) {
66
- debug$1("parsing story ID from git branch:", branch);
67
- const id = parseInt(branch.match(/\*.*/)[0].match(/\/(ch|sc-)([0-9]+)/)[2], 10);
68
- debug$1("parsed story ID from git branch:", id);
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);
69
103
  if (id) gitID.push(id.toString());
70
104
  } else {
71
105
  stopSpinner();
@@ -74,13 +108,14 @@ const main = async () => {
74
108
  }
75
109
  }
76
110
  program.args.map((a) => (a.match(/\d+/) || [])[0]).concat(gitID).map(async (_id) => {
111
+ if (!_id) return;
77
112
  const id = parseInt(_id, 10);
78
113
  let story;
79
114
  try {
80
- if (program.comment) {
81
- debug$1("request comment create");
82
- await require_lib_client.default.createStoryComment(id, { text: program.comment });
83
- debug$1("response comment create");
115
+ if (opts.comment) {
116
+ debug("request comment create");
117
+ await client.createStoryComment(id, { text: opts.comment });
118
+ debug("response comment create");
84
119
  }
85
120
  } catch (e) {
86
121
  stopSpinner();
@@ -88,10 +123,10 @@ const main = async () => {
88
123
  process.exit(3);
89
124
  }
90
125
  try {
91
- if (program.task) {
92
- debug$1("request task create");
93
- await require_lib_client.default.createTask(id, { description: program.task });
94
- debug$1("response task create");
126
+ if (opts.task) {
127
+ debug("request task create");
128
+ await client.createTask(id, { description: opts.task });
129
+ debug("response task create");
95
130
  }
96
131
  } catch (e) {
97
132
  stopSpinner();
@@ -99,23 +134,27 @@ const main = async () => {
99
134
  process.exit(3);
100
135
  }
101
136
  try {
102
- debug$1("request story");
103
- story = await require_lib_client.default.getStory(id).then((r) => r.data);
104
- debug$1("response story");
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
+ }
105
144
  } catch (e) {
106
145
  stopSpinner();
107
146
  logError("Error fetching story", id);
108
147
  process.exit(4);
109
148
  }
110
149
  try {
111
- if (program.taskComplete) {
112
- debug$1("calculating task(s) to complete");
113
- const descMatch = new RegExp(program.taskComplete, "i");
150
+ if (opts.taskComplete) {
151
+ debug("calculating task(s) to complete");
152
+ const descMatch = new RegExp(opts.taskComplete, "i");
114
153
  const tasks = story.tasks.filter((t) => t.description.match(descMatch));
115
154
  const updatedTaskIds = tasks.map((t) => t.id);
116
- debug$1("request tasks complete", updatedTaskIds);
117
- await Promise.all(tasks.map((t) => require_lib_client.default.updateTask(id, t.id, { complete: !t.complete })));
118
- debug$1("response tasks complete");
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");
119
158
  story.tasks = story.tasks.map((t) => {
120
159
  if (updatedTaskIds.indexOf(t.id) > -1) t.complete = !t.complete;
121
160
  return t;
@@ -129,22 +168,22 @@ const main = async () => {
129
168
  try {
130
169
  if (hasUpdate) {
131
170
  if (hasPositionUpdate) {
132
- debug$1("calculating move up/down");
133
- const siblings = await require_lib_stories.default.listStories({
171
+ debug("calculating move up/down");
172
+ const siblings = await stories_default.listStories({
134
173
  state: story.workflow_state_id.toString(),
135
174
  sort: "state.position:asc,position:asc"
136
175
  });
137
176
  const siblingIds = siblings.map((s) => s.id);
138
177
  const storyIndex = siblingIds.indexOf(~~id);
139
- if (program.moveAfter) update.after_id = ~~program.moveAfter;
140
- else if (program.moveBefore) update.before_id = ~~program.moveBefore;
141
- else if (program.moveUp) update.before_id = siblingIds[Math.max(0, storyIndex - (~~program.moveUp || 1))];
142
- else if (program.moveDown) update.after_id = siblingIds[Math.min(siblings.length - 1, storyIndex + (~~program.moveDown || 1))];
143
- debug$1("constructed story position update", update);
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);
144
183
  }
145
- debug$1("request story update");
146
- const changed = await require_lib_client.default.updateStory(id, update);
147
- debug$1("response story update");
184
+ debug("request story update");
185
+ const changed = await client.updateStory(id, update).then((r) => r.data);
186
+ debug("response story update");
148
187
  story = Object.assign({}, story, changed);
149
188
  }
150
189
  } catch (e) {
@@ -152,61 +191,268 @@ const main = async () => {
152
191
  logError("Error updating story", id);
153
192
  process.exit(5);
154
193
  }
155
- if (story) story = require_lib_stories.default.hydrateStory(entities, story);
156
- if (!program.idonly) spin.stop(true);
194
+ if (story) story = stories_default.hydrateStory(entities, story);
195
+ if (!opts.idonly) spin.stop(true);
157
196
  if (story) {
158
197
  printStory(story, entities);
159
- if (program.open) openURL(require_lib_stories.default.storyURL(story));
160
- if (program.openEpic) {
198
+ if (opts.open) openURL(stories_default.storyURL(story));
199
+ if (opts.openEpic) {
161
200
  if (!story.epic_id) {
162
201
  logError("This story is not part of an epic.");
163
202
  process.exit(21);
164
203
  }
165
- openURL(require_lib_stories.default.buildURL("epic", story.epic_id));
204
+ openURL(stories_default.buildURL("epic", story.epic_id));
166
205
  }
167
- if (program.openIteration) {
206
+ if (opts.openIteration) {
168
207
  if (!story.iteration_id) {
169
208
  logError("This story is not part of an iteration.");
170
209
  process.exit(22);
171
210
  }
172
- openURL(require_lib_stories.default.buildURL("iteration", story.iteration_id));
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));
173
215
  }
174
- if (program.openProject) openURL(require_lib_stories.default.buildURL("project", story.project_id));
175
216
  }
176
- if (program.download) downloadFiles(story);
177
- if (story && program.gitBranch) {
217
+ if (opts.download) downloadFiles(story);
218
+ if (story && opts.gitBranch) {
178
219
  if (!config.mentionName) {
179
220
  stopSpinner();
180
- require_lib_stories.default.checkoutStoryBranch(story, `${story.story_type}-${story.id}-`);
221
+ stories_default.checkoutStoryBranch(story, `${story.story_type}-${story.id}-`);
181
222
  logError("Error creating story branch in Shortcut format");
182
223
  logError("Please run: \"short install --force\" to add your mention name to the config.");
183
224
  process.exit(10);
184
225
  }
185
- require_lib_stories.default.checkoutStoryBranch(story);
186
- } else if (story && program.gitBranchShort) require_lib_stories.default.checkoutStoryBranch(story, `${config.mentionName}/sc-${story.id}/`);
226
+ stories_default.checkoutStoryBranch(story);
227
+ } else if (story && opts.gitBranchShort) stories_default.checkoutStoryBranch(story, `${config.mentionName}/sc-${story.id}/`);
187
228
  });
188
229
  stopSpinner();
189
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
+ }
190
346
  const openURL = (url) => {
191
- (0, child_process.execSync)(`${os.default.platform() === "darwin" ? "open" : "xdg-open"} '${url}'`);
347
+ execSync(`${os.platform() === "darwin" ? "open" : "xdg-open"} '${url}'`);
192
348
  };
193
349
  const stopSpinner = () => {
194
- if (!(program.idonly || program.quiet)) spin.stop(true);
350
+ if (!(opts.idonly || opts.quiet)) spin.stop(true);
195
351
  };
196
352
  const downloadFiles = (story) => story.files.map((file) => {
197
- https.default.get(require_lib_stories.default.fileURL(file), (res) => {
198
- const filePath = path.default.join(program.downloadDir, file.name);
199
- log(chalk.default.bold("Downloading file to: ") + filePath);
200
- const stream = fs.default.createWriteStream(filePath);
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);
201
357
  res.pipe(stream);
202
358
  stream.on("finish", () => stream.close());
203
359
  });
204
360
  });
205
361
  const printStory = (story, entities) => {
206
- if (program.idonly) return log(story.id);
207
- if (program.format) return require_lib_stories.default.printFormattedStory(program)(story);
208
- require_lib_stories.default.printDetailedStory(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();
209
455
  };
210
456
  main();
211
-
212
- //#endregion
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 {};