@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,87 @@
1
+ #!/usr/bin/env node
2
+ import { loadCachedConfig, updateConfig } from "../lib/configure.js";
3
+ import { version } from "../package.js";
4
+ import { Command } from "commander";
5
+ import { ShortcutClient } from "@shortcut/client";
6
+ import { createInterface } from "readline/promises";
7
+ import { Writable } from "stream";
8
+ //#region src/bin/short-install.ts
9
+ const extant = loadCachedConfig();
10
+ const log = console.log;
11
+ const TOKEN_PROMPT = "Enter your Shortcut API token. You can get one at https://app.shortcut.com/settings/account/api-tokens\nAPI token: ";
12
+ const opts = new Command().version(version).description("Install access token and other settings for the Shortcut API").option("-f, --force", "Force install/reinstall").option("-r, --refresh", "Refresh the configuration with details from Shortcut.").option("-t, --token <token>", "Shortcut API token to save without prompting").parse(process.argv).opts();
13
+ const enrichConfigWithMemberDetails = async (config) => {
14
+ log("Fetching user/member details from Shortcut...");
15
+ const clientConfig = {};
16
+ if (process.env.SHORTCUT_API_BASE_URL) clientConfig.baseURL = process.env.SHORTCUT_API_BASE_URL;
17
+ const member = await new ShortcutClient(config.token ?? "", clientConfig).getCurrentMemberInfo().then((r) => r.data);
18
+ return {
19
+ ...config,
20
+ mentionName: member.mention_name,
21
+ urlSlug: member.workspace2.url_slug
22
+ };
23
+ };
24
+ var MaskedWritable = class extends Writable {
25
+ muted = false;
26
+ _write(chunk, encoding, callback) {
27
+ const text = typeof chunk === "string" ? chunk : chunk.toString();
28
+ if (!this.muted) {
29
+ process.stdout.write(text);
30
+ callback();
31
+ return;
32
+ }
33
+ if (text.includes("\n")) process.stdout.write("\n");
34
+ callback();
35
+ }
36
+ };
37
+ const promptForToken = async () => {
38
+ const output = new MaskedWritable();
39
+ const rl = createInterface({
40
+ input: process.stdin,
41
+ output,
42
+ terminal: true
43
+ });
44
+ try {
45
+ process.stdout.write(TOKEN_PROMPT);
46
+ output.muted = true;
47
+ const token = (await rl.question("")).trim();
48
+ output.muted = false;
49
+ process.stdout.write("\n");
50
+ return token;
51
+ } catch (error) {
52
+ output.muted = false;
53
+ process.stdout.write("\n");
54
+ if (error instanceof Error && "code" in error && error.code === "ABORT_ERR") process.exit(130);
55
+ throw error;
56
+ } finally {
57
+ rl.close();
58
+ }
59
+ };
60
+ const resolveToken = async () => {
61
+ if (opts.token) return opts.token.trim();
62
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
63
+ console.error("No API token provided. Pass --token when running non-interactively.");
64
+ process.exit(1);
65
+ }
66
+ return promptForToken();
67
+ };
68
+ const main = async () => {
69
+ if (opts.refresh) updateConfig(await enrichConfigWithMemberDetails(extant));
70
+ else if (!extant.token || opts.force) {
71
+ const token = await resolveToken();
72
+ if (!token) {
73
+ console.error("No API token provided.");
74
+ process.exit(1);
75
+ }
76
+ const config = await enrichConfigWithMemberDetails({
77
+ ...extant,
78
+ token
79
+ });
80
+ log("Saving config...");
81
+ if (updateConfig(config)) log("Saved config");
82
+ else log("Error saving config");
83
+ } else if (extant.token) log("A configuration/token is already saved. To override, re-run with --force");
84
+ };
85
+ main();
86
+ //#endregion
87
+ export {};
@@ -1,19 +1,16 @@
1
1
  #!/usr/bin/env node
2
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
3
- const require_lib_spinner = require('../lib/spinner.cjs');
4
- const require_lib_configure = require('../lib/configure.cjs');
5
- const require_lib_client = require('../lib/client.cjs');
6
- const require_lib_stories = require('../lib/stories.cjs');
7
- let commander = require("commander");
8
- let child_process = require("child_process");
9
- let chalk = require("chalk");
10
- chalk = require_rolldown_runtime.__toESM(chalk);
11
-
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 { exec } from "child_process";
8
+ import chalk from "chalk";
12
9
  //#region src/bin/short-iteration.ts
13
- const config = require_lib_configure.loadConfig();
14
- const spin = require_lib_spinner.default();
10
+ const config = loadConfig();
11
+ const spin = spinner();
15
12
  const log = console.log;
16
- const program = new commander.Command().usage("[command] [options]").description("view, create, update, or delete iterations");
13
+ const program = new Command().usage("[command] [options]").description("view, create, update, or delete iterations");
17
14
  program.command("view <id>").description("view an iteration by id").option("-O, --open", "Open iteration in browser").action(viewIteration);
18
15
  program.command("create").description("create a new iteration").option("-n, --name [text]", "Set name of iteration, required", "").option("-d, --description [text]", "Set description of iteration", "").option("--start-date [date]", "Set start date (YYYY-MM-DD), required", "").option("--end-date [date]", "Set end date (YYYY-MM-DD), required", "").option("-T, --team [id|name]", "Set team/group of iteration", "").option("-I, --idonly", "Print only ID of iteration result").option("-O, --open", "Open iteration in browser").action(createIteration);
19
16
  program.command("update <id>").description("update an existing iteration").option("-n, --name [text]", "Set name of iteration", "").option("-d, --description [text]", "Set description of iteration", "").option("--start-date [date]", "Set start date (YYYY-MM-DD)", "").option("--end-date [date]", "Set end date (YYYY-MM-DD)", "").option("-T, --team [id|name]", "Set team/group of iteration", "").option("-O, --open", "Open iteration in browser").action(updateIteration);
@@ -23,10 +20,10 @@ program.parse(process.argv);
23
20
  async function viewIteration(id, options) {
24
21
  spin.start();
25
22
  try {
26
- const iteration = await require_lib_client.default.getIteration(parseInt(id, 10)).then((r) => r.data);
23
+ const iteration = await client.getIteration(parseInt(id, 10)).then((r) => r.data);
27
24
  spin.stop(true);
28
25
  printIteration(iteration);
29
- if (options.open) (0, child_process.exec)(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
26
+ if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
30
27
  } catch (e) {
31
28
  spin.stop(true);
32
29
  const error = e;
@@ -36,7 +33,7 @@ async function viewIteration(id, options) {
36
33
  }
37
34
  }
38
35
  async function createIteration(options) {
39
- const entities = await require_lib_stories.default.fetchEntities();
36
+ const entities = await stories_default.fetchEntities();
40
37
  if (!options.idonly) spin.start();
41
38
  const iterationData = {
42
39
  name: options.name || "",
@@ -45,7 +42,7 @@ async function createIteration(options) {
45
42
  };
46
43
  if (options.description) iterationData.description = options.description;
47
44
  if (options.team) {
48
- const group = require_lib_stories.default.findGroup(entities, options.team);
45
+ const group = stories_default.findGroup(entities, options.team);
49
46
  if (group?.id) iterationData.group_ids = [group.id];
50
47
  }
51
48
  if (!iterationData.name) {
@@ -65,7 +62,7 @@ async function createIteration(options) {
65
62
  }
66
63
  let iteration;
67
64
  try {
68
- iteration = await require_lib_client.default.createIteration(iterationData).then((r) => r.data);
65
+ iteration = await client.createIteration(iterationData).then((r) => r.data);
69
66
  } catch (e) {
70
67
  if (!options.idonly) spin.stop(true);
71
68
  log("Error creating iteration:", e.message ?? String(e));
@@ -75,11 +72,11 @@ async function createIteration(options) {
75
72
  if (iteration) if (options.idonly) log(iteration.id);
76
73
  else {
77
74
  printIteration(iteration);
78
- if (options.open) (0, child_process.exec)(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
75
+ if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
79
76
  }
80
77
  }
81
78
  async function updateIteration(id, options) {
82
- const entities = await require_lib_stories.default.fetchEntities();
79
+ const entities = await stories_default.fetchEntities();
83
80
  spin.start();
84
81
  const updateData = {};
85
82
  if (options.name) updateData.name = options.name;
@@ -87,7 +84,7 @@ async function updateIteration(id, options) {
87
84
  if (options.startDate) updateData.start_date = options.startDate;
88
85
  if (options.endDate) updateData.end_date = options.endDate;
89
86
  if (options.team) {
90
- const group = require_lib_stories.default.findGroup(entities, options.team);
87
+ const group = stories_default.findGroup(entities, options.team);
91
88
  if (group?.id) updateData.group_ids = [group.id];
92
89
  }
93
90
  if (Object.keys(updateData).length === 0) {
@@ -97,7 +94,7 @@ async function updateIteration(id, options) {
97
94
  }
98
95
  let iteration;
99
96
  try {
100
- iteration = await require_lib_client.default.updateIteration(parseInt(id, 10), updateData).then((r) => r.data);
97
+ iteration = await client.updateIteration(parseInt(id, 10), updateData).then((r) => r.data);
101
98
  } catch (e) {
102
99
  spin.stop(true);
103
100
  const error = e;
@@ -108,13 +105,13 @@ async function updateIteration(id, options) {
108
105
  spin.stop(true);
109
106
  if (iteration) {
110
107
  printIteration(iteration);
111
- if (options.open) (0, child_process.exec)(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
108
+ if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
112
109
  }
113
110
  }
114
111
  async function deleteIteration(id) {
115
112
  spin.start();
116
113
  try {
117
- await require_lib_client.default.deleteIteration(parseInt(id, 10));
114
+ await client.deleteIteration(parseInt(id, 10));
118
115
  spin.stop(true);
119
116
  log(`Iteration #${id} deleted successfully`);
120
117
  } catch (e) {
@@ -128,21 +125,21 @@ async function deleteIteration(id) {
128
125
  async function listIterationStories(id, options) {
129
126
  spin.start();
130
127
  try {
131
- const [stories, entities] = await Promise.all([require_lib_client.default.listIterationStories(parseInt(id, 10)).then((r) => r.data), require_lib_stories.default.fetchEntities()]);
128
+ const [stories, entities] = await Promise.all([client.listIterationStories(parseInt(id, 10)).then((r) => r.data), stories_default.fetchEntities()]);
132
129
  spin.stop(true);
133
130
  if (stories.length === 0) {
134
131
  log(`No stories found in iteration #${id}`);
135
132
  return;
136
133
  }
137
- log(chalk.default.bold(`Stories in iteration #${id}:`));
134
+ log(chalk.bold(`Stories in iteration #${id}:`));
138
135
  log();
139
136
  stories.forEach((story) => {
140
- const hydrated = require_lib_stories.default.hydrateStory(entities, story);
141
- if (options.format) require_lib_stories.default.printFormattedStory({ format: options.format })(hydrated);
137
+ const hydrated = stories_default.hydrateStory(entities, story);
138
+ if (options.format) stories_default.printFormattedStory({ format: options.format })(hydrated);
142
139
  else {
143
140
  const state = hydrated.state?.name ?? "Unknown";
144
141
  const owners = hydrated.owners?.map((o) => o?.profile.mention_name).filter(Boolean).join(", ");
145
- log(`${chalk.default.bold("#" + story.id)} ${chalk.default.blue(story.name)}`);
142
+ log(`${chalk.bold("#" + story.id)} ${chalk.blue(story.name)}`);
146
143
  log(` Type: ${story.story_type} | State: ${state} | Owners: ${owners || "_"}`);
147
144
  log(` Points: ${story.estimate ?? "_"}`);
148
145
  log();
@@ -160,25 +157,24 @@ function printIteration(iteration) {
160
157
  const stats = iteration.stats;
161
158
  const totalStories = stats.num_stories_done + stats.num_stories_started + stats.num_stories_unstarted + stats.num_stories_backlog;
162
159
  const completionPct = stats.num_points > 0 ? Math.round(stats.num_points_done / stats.num_points * 100) : 0;
163
- log(chalk.default.blue.bold(`#${iteration.id}`) + chalk.default.blue(` ${iteration.name}`));
164
- if (iteration.description) log(chalk.default.bold("Description:") + ` ${iteration.description}`);
165
- log(chalk.default.bold("Status:") + ` ${formatStatus(iteration.status)}`);
166
- log(chalk.default.bold("Start Date:") + ` ${iteration.start_date}`);
167
- log(chalk.default.bold("End Date:") + ` ${iteration.end_date}`);
168
- if (iteration.group_ids && iteration.group_ids.length > 0) log(chalk.default.bold("Teams:") + ` ${iteration.group_ids.join(", ")}`);
169
- log(chalk.default.bold("Stories:") + ` ${totalStories} (${stats.num_stories_done} done)`);
170
- log(chalk.default.bold("Points:") + ` ${stats.num_points} (${stats.num_points_done} done)`);
171
- log(chalk.default.bold("Completion:") + ` ${completionPct}%`);
172
- log(chalk.default.bold("URL:") + ` ${iteration.app_url}`);
160
+ log(chalk.blue.bold(`#${iteration.id}`) + chalk.blue(` ${iteration.name}`));
161
+ if (iteration.description) log(chalk.bold("Description:") + ` ${iteration.description}`);
162
+ log(chalk.bold("Status:") + ` ${formatStatus(iteration.status)}`);
163
+ log(chalk.bold("Start Date:") + ` ${iteration.start_date}`);
164
+ log(chalk.bold("End Date:") + ` ${iteration.end_date}`);
165
+ if (iteration.group_ids && iteration.group_ids.length > 0) log(chalk.bold("Teams:") + ` ${iteration.group_ids.join(", ")}`);
166
+ log(chalk.bold("Stories:") + ` ${totalStories} (${stats.num_stories_done} done)`);
167
+ log(chalk.bold("Points:") + ` ${stats.num_points} (${stats.num_points_done} done)`);
168
+ log(chalk.bold("Completion:") + ` ${completionPct}%`);
169
+ log(chalk.bold("URL:") + ` ${iteration.app_url}`);
173
170
  log();
174
171
  }
175
172
  function formatStatus(status) {
176
173
  switch (status) {
177
- case "started": return chalk.default.green(status);
178
- case "done": return chalk.default.gray(status);
179
- case "unstarted":
180
- default: return chalk.default.yellow(status);
174
+ case "started": return chalk.green(status);
175
+ case "done": return chalk.gray(status);
176
+ default: return chalk.yellow(status);
181
177
  }
182
178
  }
183
-
184
- //#endregion
179
+ //#endregion
180
+ export {};
@@ -1,20 +1,17 @@
1
1
  #!/usr/bin/env node
2
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
3
- const require_lib_spinner = require('../lib/spinner.cjs');
4
- const require_lib_configure = require('../lib/configure.cjs');
5
- const require_lib_client = require('../lib/client.cjs');
6
- let commander = require("commander");
7
- let chalk = require("chalk");
8
- chalk = require_rolldown_runtime.__toESM(chalk);
9
-
2
+ import { loadConfig } from "../lib/configure.js";
3
+ import client from "../lib/client.js";
4
+ import spinner from "../lib/spinner.js";
5
+ import { Command } from "commander";
6
+ import chalk from "chalk";
10
7
  //#region src/bin/short-iterations.ts
11
- const config = require_lib_configure.loadConfig();
8
+ const config = loadConfig();
12
9
  const log = console.log;
13
- const spin = require_lib_spinner.default();
14
- const opts = new commander.Command().description("Display iterations available for stories").option("-S, --status [status]", "Filter by status (unstarted, started, done)", "").option("-T, --team [id|name]", "Filter by team/group id or name", "").option("-C, --current", "Show only current/active iterations", false).option("-t, --title [query]", "Filter iterations with name containing query", "").option("-d, --detailed", "Show more details for each iteration", false).option("-f, --format [template]", "Format each iteration output by template", "").parse(process.argv).opts();
10
+ const spin = spinner();
11
+ const opts = new Command().description("Display iterations available for stories").option("-S, --status [status]", "Filter by status (unstarted, started, done)", "").option("-T, --team [id|name]", "Filter by team/group id or name", "").option("-C, --current", "Show only current/active iterations", false).option("-t, --title [query]", "Filter iterations with name containing query", "").option("-d, --detailed", "Show more details for each iteration", false).option("-f, --format [template]", "Format each iteration output by template", "").parse(process.argv).opts();
15
12
  const main = async () => {
16
13
  spin.start();
17
- const [iterations, groups] = await Promise.all([require_lib_client.default.listIterations(null).then((r) => r.data), require_lib_client.default.listGroups().then((r) => r.data)]);
14
+ const [iterations, groups] = await Promise.all([client.listIterations().then((r) => r.data), client.listGroups().then((r) => r.data)]);
18
15
  spin.stop(true);
19
16
  const textMatch = new RegExp(opts.title ?? "", "i");
20
17
  const statusMatch = opts.status ? new RegExp(opts.status, "i") : null;
@@ -51,16 +48,15 @@ const printItem = (iteration, groupsById) => {
51
48
  const format = opts.format || defaultFormat;
52
49
  const url = `https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`;
53
50
  const completionPct = stats.num_points > 0 ? Math.round(stats.num_points_done / stats.num_points * 100) : 0;
54
- log(format.replace(/%id/, chalk.default.bold(`${iteration.id}`)).replace(/%t/, chalk.default.blue(`${iteration.name}`)).replace(/%s/, formatStatus(iteration.status)).replace(/%start/, `${iteration.start_date}`).replace(/%end/, `${iteration.end_date}`).replace(/%teams/, groups.join(", ") || "_").replace(/%stories/, `${totalStories}`).replace(/%done/, `${stats.num_stories_done}`).replace(/%points/, `${stats.num_points}`).replace(/%pdone/, `${stats.num_points_done}`).replace(/%completion/, `${completionPct}`).replace(/%url/, url));
51
+ log(format.replace(/%id/, chalk.bold(`${iteration.id}`)).replace(/%t/, chalk.blue(`${iteration.name}`)).replace(/%s/, formatStatus(iteration.status)).replace(/%start/, `${iteration.start_date}`).replace(/%end/, `${iteration.end_date}`).replace(/%teams/, groups.join(", ") || "_").replace(/%stories/, `${totalStories}`).replace(/%done/, `${stats.num_stories_done}`).replace(/%points/, `${stats.num_points}`).replace(/%pdone/, `${stats.num_points_done}`).replace(/%completion/, `${completionPct}`).replace(/%url/, url));
55
52
  };
56
53
  const formatStatus = (status) => {
57
54
  switch (status) {
58
- case "started": return chalk.default.green(status);
59
- case "done": return chalk.default.gray(status);
60
- case "unstarted":
61
- default: return chalk.default.yellow(status);
55
+ case "started": return chalk.green(status);
56
+ case "done": return chalk.gray(status);
57
+ default: return chalk.yellow(status);
62
58
  }
63
59
  };
64
60
  main();
65
-
66
- //#endregion
61
+ //#endregion
62
+ export {};
@@ -0,0 +1,130 @@
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-label.ts
8
+ const spin = spinner();
9
+ const log = console.log;
10
+ const program = new Command().usage("[command] [options]").description("create labels or view stories for a label");
11
+ program.command("create").description("create a new label").option("-n, --name [text]", "Set name of label, required", "").option("-d, --description [text]", "Set description of label", "").option("-c, --color [hex]", "Set label color in hex format like #3366cc", "").option("-I, --idonly", "Print only ID of label result").action(createLabel);
12
+ program.command("update <idOrName>").description("update an existing label").option("-n, --name [text]", "Set name of label", "").option("-d, --description [text]", "Set description of label", "").option("-c, --color [hex]", "Set label color in hex format like #3366cc", "").option("-a, --archived", "Archive label").action(updateLabel);
13
+ program.command("stories <idOrName>").description("list stories for a label by id or name").option("-d, --detailed", "Show more details for each story").option("-f, --format [template]", "Format each story output by template", "").action(listLabelStories);
14
+ program.command("epics <idOrName>").description("list epics for a label by id or name").action(listLabelEpics);
15
+ program.parse(process.argv);
16
+ async function createLabel(options) {
17
+ if (!options.name) {
18
+ log("Must provide --name");
19
+ process.exit(1);
20
+ }
21
+ if (!options.idonly) spin.start();
22
+ try {
23
+ const input = { name: options.name };
24
+ if (options.description) input.description = options.description;
25
+ if (options.color) input.color = options.color;
26
+ const label = await client.createLabel(input).then((r) => r.data);
27
+ if (!options.idonly) spin.stop(true);
28
+ if (options.idonly) {
29
+ log(label.id);
30
+ return;
31
+ }
32
+ printLabel(label);
33
+ } catch (e) {
34
+ if (!options.idonly) spin.stop(true);
35
+ log(`Error creating label: ${e.message ?? String(e)}`);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ async function updateLabel(idOrName, options) {
40
+ spin.start();
41
+ try {
42
+ const entities = await stories_default.fetchEntities();
43
+ const label = stories_default.findLabel(entities, idOrName);
44
+ if (!label) {
45
+ spin.stop(true);
46
+ log(`Label ${idOrName} not found`);
47
+ process.exit(1);
48
+ }
49
+ const input = {};
50
+ if (options.name) input.name = options.name;
51
+ if (options.description) input.description = options.description;
52
+ if (options.color) input.color = options.color;
53
+ if (options.archived) input.archived = true;
54
+ if (Object.keys(input).length === 0) {
55
+ spin.stop(true);
56
+ log("No updates provided. Use --name, --description, --color, or --archived");
57
+ process.exit(1);
58
+ }
59
+ const updatedLabel = await client.updateLabel(label.id, input).then((r) => r.data);
60
+ spin.stop(true);
61
+ printLabel(updatedLabel);
62
+ } catch (e) {
63
+ spin.stop(true);
64
+ log(`Error updating label: ${e.message ?? String(e)}`);
65
+ process.exit(1);
66
+ }
67
+ }
68
+ async function listLabelStories(idOrName, options) {
69
+ spin.start();
70
+ try {
71
+ const entities = await stories_default.fetchEntities();
72
+ const label = stories_default.findLabel(entities, idOrName);
73
+ if (!label) {
74
+ spin.stop(true);
75
+ log(`Label ${idOrName} not found`);
76
+ process.exit(1);
77
+ }
78
+ const stories = await client.listLabelStories(label.id).then((r) => r.data);
79
+ spin.stop(true);
80
+ if (stories.length === 0) {
81
+ log(`No stories found for label #${label.id} ${label.name}`);
82
+ return;
83
+ }
84
+ stories.map((story) => stories_default.hydrateStory(entities, story)).forEach(options.detailed ? (story) => stories_default.printDetailedStory(story, entities) : stories_default.printFormattedStory({ format: options.format }));
85
+ } catch (e) {
86
+ spin.stop(true);
87
+ log(`Error fetching label stories: ${e.message ?? String(e)}`);
88
+ process.exit(1);
89
+ }
90
+ }
91
+ async function listLabelEpics(idOrName) {
92
+ spin.start();
93
+ try {
94
+ const entities = await stories_default.fetchEntities();
95
+ const label = stories_default.findLabel(entities, idOrName);
96
+ if (!label) {
97
+ spin.stop(true);
98
+ log(`Label ${idOrName} not found`);
99
+ process.exit(1);
100
+ }
101
+ const epics = await client.listLabelEpics(label.id).then((r) => r.data);
102
+ spin.stop(true);
103
+ if (epics.length === 0) {
104
+ log(`No epics found for label #${label.id} ${label.name}`);
105
+ return;
106
+ }
107
+ epics.forEach(printEpic);
108
+ } catch (e) {
109
+ spin.stop(true);
110
+ log(`Error fetching label epics: ${e.message ?? String(e)}`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+ function printLabel(label) {
115
+ log(chalk.bold(`#${label.id}`) + chalk.blue(` ${label.name}`));
116
+ if (label.color) log(chalk.bold("Color: ") + ` ${label.color}`);
117
+ if (label.description) log(chalk.bold("Description: ") + ` ${label.description}`);
118
+ if (label.archived) log(chalk.bold("Archived: ") + ` ${label.archived}`);
119
+ log();
120
+ }
121
+ function printEpic(epic) {
122
+ log(chalk.bold(`#${epic.id}`) + chalk.blue(` ${epic.name}`));
123
+ log(chalk.bold("State: ") + ` ${epic.state}`);
124
+ log(chalk.bold("Started: ") + ` ${epic.started}`);
125
+ log(chalk.bold("Completed: ") + ` ${epic.completed}`);
126
+ log(chalk.bold("URL: ") + ` ${epic.app_url}`);
127
+ log();
128
+ }
129
+ //#endregion
130
+ export {};
@@ -0,0 +1,27 @@
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-labels.ts
7
+ const spin = spinner();
8
+ const log = console.log;
9
+ const opts = new Command().description("Display labels available for stories and epics").option("-a, --archived", "List labels including archived", "").option("-s, --search [query]", "List labels with name containing query", "").parse(process.argv).opts();
10
+ async function main() {
11
+ spin.start();
12
+ const labels = await client.listLabels().then((r) => r.data);
13
+ spin.stop(true);
14
+ const searchMatch = new RegExp(opts.search ?? "", "i");
15
+ labels.filter((label) => !!`${label.id} ${label.name}`.match(searchMatch)).forEach(printLabel);
16
+ }
17
+ function printLabel(label) {
18
+ if (label.archived && !opts.archived) return;
19
+ log(chalk.bold(`#${label.id}`) + chalk.blue(` ${label.name}`));
20
+ if (label.color) log(chalk.bold("Color: ") + ` ${label.color}`);
21
+ if (label.description) log(chalk.bold("Description: ") + ` ${label.description}`);
22
+ if (label.archived) log(chalk.bold("Archived: ") + ` ${label.archived}`);
23
+ log();
24
+ }
25
+ main();
26
+ //#endregion
27
+ export {};
@@ -0,0 +1,31 @@
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-members.ts
7
+ const spin = spinner("Loading... %s ");
8
+ const log = console.log;
9
+ const opts = new Command().description("Display members available for stories").option("-s, --search [query]", "List members with name containing query", "").option("-d, --disabled", "List members including disabled", "").parse(process.argv).opts();
10
+ const main = async () => {
11
+ spin.start();
12
+ const members = await client.listMembers().then((r) => r.data);
13
+ spin.stop(true);
14
+ const ownerMatch = new RegExp(opts.search ?? "", "i");
15
+ members.filter((o) => {
16
+ return !!`${o.profile.name} ${o.profile.mention_name}`.match(ownerMatch);
17
+ }).map(printMember);
18
+ };
19
+ const printMember = (member) => {
20
+ if (member.disabled && !opts.disabled) return;
21
+ log(chalk.bold(`#${member.id}`));
22
+ log(chalk.bold("Name: ") + ` ${member.profile.name}`);
23
+ log(chalk.bold("Mention Name: ") + ` ${member.profile.mention_name}`);
24
+ log(chalk.bold("Role: ") + ` ${member.role}`);
25
+ log(chalk.bold("Email: ") + ` ${member.profile.email_address}`);
26
+ if (member.disabled) log(chalk.bold("Disabled: ") + ` ${member.disabled}`);
27
+ log();
28
+ };
29
+ main();
30
+ //#endregion
31
+ export {};