hyouji 0.0.17 → 0.1.1

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 (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +466 -78
  3. package/package.json +7 -8
package/README.md CHANGED
@@ -20,7 +20,9 @@ https://levelup.gitconnected.com/create-github-labels-from-terminal-158d4868fab
20
20
 
21
21
  ![hyouji_terminal](./hyouji.png)
22
22
 
23
- https://github.com/user-attachments/assets/739f185a-1bd0-411b-8947-dd4600c452c8
23
+ https://github.com/user-attachments/assets/d7960f58-031e-4c08-86ff-f14762915c7f
24
+
25
+
24
26
 
25
27
  ### Labels API
26
28
 
package/dist/index.js CHANGED
@@ -7,7 +7,9 @@ import { homedir } from "os";
7
7
  import * as path from "path";
8
8
  import { join, dirname } from "path";
9
9
  import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
10
- import prompts from "prompts";
10
+ import { stdout, stdin } from "node:process";
11
+ import { emitKeypressEvents } from "node:readline";
12
+ import { createInterface } from "node:readline/promises";
11
13
  import YAML from "yaml";
12
14
  import { Octokit } from "@octokit/core";
13
15
  import { exec } from "child_process";
@@ -47,26 +49,16 @@ const newLabel = [
47
49
  }
48
50
  ];
49
51
  const deleteLabel$1 = {
50
- type: "text",
51
- name: "name",
52
52
  message: "Please type label name you want to delete"
53
53
  };
54
54
  const labelFilePath = {
55
- type: "text",
56
- name: "filePath",
57
55
  message: "Please type the path to your JSON or YAML file"
58
56
  };
59
57
  const dryRunToggle = {
60
- type: "toggle",
61
- name: "dryRun",
62
58
  message: "Run in dry-run mode? (no API calls will be made)",
63
- active: "yes",
64
- inactive: "no",
65
59
  initial: false
66
60
  };
67
61
  const actionSelector = {
68
- type: "multiselect",
69
- name: "action",
70
62
  message: "Please select an action",
71
63
  choices: [
72
64
  { title: "create a label", value: 0 },
@@ -81,8 +73,6 @@ const actionSelector = {
81
73
  ]
82
74
  };
83
75
  const holdToken = {
84
- type: "confirm",
85
- name: "value",
86
76
  message: "Do you have a personal token?",
87
77
  initial: true
88
78
  };
@@ -1019,13 +1009,374 @@ class ConfigManager {
1019
1009
  ].includes(error.type);
1020
1010
  }
1021
1011
  }
1012
+ const OPEN_TUI_INPUT_TIMEOUT_MS = 3e4;
1013
+ const ESCAPE_SELECTION_VALUE = 99;
1014
+ let opentuiLoadAttempted = false;
1015
+ let opentuiModule = null;
1016
+ const loadOpenTui = async () => {
1017
+ if (opentuiLoadAttempted) {
1018
+ return opentuiModule;
1019
+ }
1020
+ opentuiLoadAttempted = true;
1021
+ try {
1022
+ const loaded = await import("@opentui/core");
1023
+ opentuiModule = loaded;
1024
+ return loaded;
1025
+ } catch {
1026
+ opentuiModule = null;
1027
+ return null;
1028
+ }
1029
+ };
1030
+ const question = async (message) => {
1031
+ const rl = createInterface({ input: stdin, output: stdout });
1032
+ try {
1033
+ const answer = await rl.question(`${message}: `);
1034
+ return answer.trim();
1035
+ } finally {
1036
+ rl.close();
1037
+ }
1038
+ };
1039
+ const askText = async (message, options = {}) => {
1040
+ const core = await loadOpenTui();
1041
+ if (core) {
1042
+ const result = await askTextWithOpenTui(core, message, options);
1043
+ if (result !== null) {
1044
+ return result;
1045
+ }
1046
+ }
1047
+ const answer = await question(message);
1048
+ if (answer.length === 0 && options.initial !== void 0) {
1049
+ return options.initial;
1050
+ }
1051
+ return answer;
1052
+ };
1053
+ const askPassword = async (message) => {
1054
+ const masked = await askPasswordWithRawInput(message);
1055
+ if (masked !== null) {
1056
+ return masked;
1057
+ }
1058
+ return askText(message);
1059
+ };
1060
+ const askConfirm = async (message, initial = true) => {
1061
+ const value = await askSelect(message, [
1062
+ { title: initial ? "yes (default)" : "yes", value: 1 },
1063
+ { title: initial ? "no" : "no (default)", value: 0 }
1064
+ ]);
1065
+ if (value === 1) {
1066
+ return true;
1067
+ }
1068
+ if (value === 0) {
1069
+ return false;
1070
+ }
1071
+ if (value === ESCAPE_SELECTION_VALUE) {
1072
+ return false;
1073
+ }
1074
+ return initial;
1075
+ };
1076
+ const askSelect = async (message, choices) => {
1077
+ const core = await loadOpenTui();
1078
+ if (core) {
1079
+ const selected = await askSelectWithOpenTui(core, message, choices);
1080
+ if (selected !== null) {
1081
+ return selected;
1082
+ }
1083
+ }
1084
+ stdout.write(`${message}
1085
+ `);
1086
+ choices.forEach((choice, index) => {
1087
+ stdout.write(` ${index + 1}. ${choice.title}
1088
+ `);
1089
+ });
1090
+ const rawSelected = await askSelectWithRawInput(choices);
1091
+ if (rawSelected !== null) {
1092
+ return rawSelected;
1093
+ }
1094
+ while (true) {
1095
+ const answer = await question("Select number");
1096
+ const normalized = answer.toLowerCase();
1097
+ if (normalized === "esc" || normalized === "escape") {
1098
+ return choices.find((choice2) => choice2.title.toLowerCase() === "exit")?.value ?? 99;
1099
+ }
1100
+ const parsed = Number.parseInt(answer, 10);
1101
+ if (Number.isNaN(parsed)) {
1102
+ stdout.write("Please enter a valid number.\n");
1103
+ continue;
1104
+ }
1105
+ const choice = choices[parsed - 1];
1106
+ if (!choice) {
1107
+ stdout.write("Out of range. Try again.\n");
1108
+ continue;
1109
+ }
1110
+ return choice.value;
1111
+ }
1112
+ };
1113
+ const askSelectWithOpenTui = async (core, message, choices) => {
1114
+ const renderer = await core.createCliRenderer({ exitOnCtrlC: true });
1115
+ let destroyed = false;
1116
+ const destroyRenderer = async () => {
1117
+ if (destroyed) {
1118
+ return;
1119
+ }
1120
+ destroyed = true;
1121
+ await renderer.destroy();
1122
+ };
1123
+ try {
1124
+ const root = new core.BoxRenderable(renderer, {
1125
+ id: "prompt-root",
1126
+ flexDirection: "column",
1127
+ border: true,
1128
+ padding: 1
1129
+ });
1130
+ const title = new core.TextRenderable(renderer, {
1131
+ id: "prompt-title",
1132
+ content: message
1133
+ });
1134
+ const select = new core.SelectRenderable(renderer, {
1135
+ id: "prompt-select",
1136
+ options: choices.map((choice) => ({
1137
+ name: choice.title,
1138
+ value: choice.value
1139
+ }))
1140
+ });
1141
+ root.add(title);
1142
+ root.add(select);
1143
+ renderer.root.add(root);
1144
+ select.focus?.();
1145
+ if (!select.on) {
1146
+ await destroyRenderer();
1147
+ return null;
1148
+ }
1149
+ const escapeValue = choices.find((choice) => choice.title.toLowerCase() === "exit")?.value ?? 99;
1150
+ return await new Promise((resolve) => {
1151
+ let resolved = false;
1152
+ const resolveOnce = async (value) => {
1153
+ if (resolved) {
1154
+ return;
1155
+ }
1156
+ resolved = true;
1157
+ await destroyRenderer();
1158
+ resolve(value);
1159
+ };
1160
+ select.on(
1161
+ core.SelectRenderableEvents.ITEM_SELECTED,
1162
+ async (_index, option) => {
1163
+ if (typeof option === "object" && option !== null && "value" in option && typeof option.value === "number") {
1164
+ await resolveOnce(option.value);
1165
+ return;
1166
+ }
1167
+ await resolveOnce(99);
1168
+ }
1169
+ );
1170
+ renderer.keyInput?.on?.("keypress", async (key) => {
1171
+ if (typeof key === "object" && key !== null && "name" in key && (key.name === "escape" || key.name === "esc")) {
1172
+ await resolveOnce(escapeValue);
1173
+ }
1174
+ });
1175
+ });
1176
+ } catch {
1177
+ await destroyRenderer();
1178
+ return null;
1179
+ }
1180
+ };
1181
+ const askSelectWithRawInput = async (choices) => {
1182
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
1183
+ return null;
1184
+ }
1185
+ const escapeValue = choices.find((choice) => choice.title.toLowerCase() === "exit")?.value ?? 99;
1186
+ stdout.write("Select number (or Esc to exit): ");
1187
+ return await new Promise((resolve) => {
1188
+ let digits = "";
1189
+ const wasRaw = stdin.isRaw;
1190
+ const wasPaused = stdin.isPaused();
1191
+ const cleanup = () => {
1192
+ stdin.removeListener("keypress", onKeypress);
1193
+ if (!wasRaw) {
1194
+ stdin.setRawMode(false);
1195
+ }
1196
+ if (wasPaused) {
1197
+ stdin.pause();
1198
+ }
1199
+ stdout.write("\n");
1200
+ };
1201
+ const resolveValue = (value) => {
1202
+ cleanup();
1203
+ resolve(value);
1204
+ };
1205
+ const onKeypress = (str, key) => {
1206
+ if (!key) {
1207
+ return;
1208
+ }
1209
+ if (key.name === "escape" || key.name === "esc") {
1210
+ resolveValue(escapeValue);
1211
+ return;
1212
+ }
1213
+ if (key.name === "return" || key.name === "enter") {
1214
+ const parsed = Number.parseInt(digits, 10);
1215
+ if (Number.isNaN(parsed)) {
1216
+ stdout.write("\nPlease enter a valid number.\nSelect number: ");
1217
+ digits = "";
1218
+ return;
1219
+ }
1220
+ const choice = choices[parsed - 1];
1221
+ if (!choice) {
1222
+ stdout.write("\nOut of range. Try again.\nSelect number: ");
1223
+ digits = "";
1224
+ return;
1225
+ }
1226
+ resolveValue(choice.value);
1227
+ return;
1228
+ }
1229
+ if (key.name === "backspace") {
1230
+ if (digits.length > 0) {
1231
+ digits = digits.slice(0, -1);
1232
+ stdout.write("\b \b");
1233
+ }
1234
+ return;
1235
+ }
1236
+ if (/^[0-9]$/.test(str)) {
1237
+ digits += str;
1238
+ stdout.write(str);
1239
+ }
1240
+ };
1241
+ emitKeypressEvents(stdin);
1242
+ stdin.setRawMode(true);
1243
+ stdin.resume();
1244
+ stdin.on("keypress", onKeypress);
1245
+ });
1246
+ };
1247
+ const askPasswordWithRawInput = async (message) => {
1248
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
1249
+ return null;
1250
+ }
1251
+ stdout.write(`${message}: `);
1252
+ return await new Promise((resolve) => {
1253
+ let value = "";
1254
+ const wasRaw = stdin.isRaw;
1255
+ const wasPaused = stdin.isPaused();
1256
+ const cleanup = () => {
1257
+ stdin.removeListener("keypress", onKeypress);
1258
+ if (!wasRaw) {
1259
+ stdin.setRawMode(false);
1260
+ }
1261
+ if (wasPaused) {
1262
+ stdin.pause();
1263
+ }
1264
+ stdout.write("\n");
1265
+ };
1266
+ const resolveValue = (password) => {
1267
+ cleanup();
1268
+ resolve(password);
1269
+ };
1270
+ const onKeypress = (str, key) => {
1271
+ if (!key) {
1272
+ return;
1273
+ }
1274
+ if (key.name === "return" || key.name === "enter") {
1275
+ resolveValue(value);
1276
+ return;
1277
+ }
1278
+ if (key.name === "backspace") {
1279
+ if (value.length > 0) {
1280
+ value = value.slice(0, -1);
1281
+ stdout.write("\b \b");
1282
+ }
1283
+ return;
1284
+ }
1285
+ if (key.name === "escape" || key.name === "esc") {
1286
+ resolveValue(null);
1287
+ return;
1288
+ }
1289
+ if (str.length === 1 && str >= " ") {
1290
+ value += str;
1291
+ stdout.write("*");
1292
+ }
1293
+ };
1294
+ emitKeypressEvents(stdin);
1295
+ stdin.setRawMode(true);
1296
+ stdin.resume();
1297
+ stdin.on("keypress", onKeypress);
1298
+ });
1299
+ };
1300
+ const askTextWithOpenTui = async (core, message, options) => {
1301
+ const renderer = await core.createCliRenderer({ exitOnCtrlC: true });
1302
+ let destroyed = false;
1303
+ let currentValue = options.initial ?? "";
1304
+ const destroyRenderer = async () => {
1305
+ if (destroyed) {
1306
+ return;
1307
+ }
1308
+ destroyed = true;
1309
+ await renderer.destroy();
1310
+ };
1311
+ try {
1312
+ const root = new core.BoxRenderable(renderer, {
1313
+ id: "prompt-root",
1314
+ flexDirection: "column",
1315
+ border: true,
1316
+ padding: 1
1317
+ });
1318
+ const title = new core.TextRenderable(renderer, {
1319
+ id: "prompt-title",
1320
+ content: message
1321
+ });
1322
+ const inputField = new core.InputRenderable(renderer, {
1323
+ id: "prompt-input",
1324
+ placeholder: options.initial ?? ""
1325
+ });
1326
+ inputField.setValue?.(currentValue);
1327
+ if (!inputField.on) {
1328
+ await destroyRenderer();
1329
+ return null;
1330
+ }
1331
+ inputField.on(core.InputRenderableEvents.CHANGE, (nextValue) => {
1332
+ if (typeof nextValue === "string") {
1333
+ currentValue = nextValue;
1334
+ }
1335
+ });
1336
+ root.add(title);
1337
+ root.add(inputField);
1338
+ renderer.root.add(root);
1339
+ inputField.focus?.();
1340
+ if (!renderer.keyInput?.on) {
1341
+ await destroyRenderer();
1342
+ return null;
1343
+ }
1344
+ return await new Promise((resolve) => {
1345
+ let settled = false;
1346
+ const timer = setTimeout(async () => {
1347
+ if (settled) {
1348
+ return;
1349
+ }
1350
+ settled = true;
1351
+ await destroyRenderer();
1352
+ resolve(null);
1353
+ }, OPEN_TUI_INPUT_TIMEOUT_MS);
1354
+ renderer.keyInput?.on?.("keypress", async (key) => {
1355
+ if (settled) {
1356
+ return;
1357
+ }
1358
+ if (typeof key === "object" && key !== null && "name" in key && (key.name === "enter" || key.name === "return")) {
1359
+ settled = true;
1360
+ clearTimeout(timer);
1361
+ await destroyRenderer();
1362
+ if (currentValue === "" && options.initial !== void 0) {
1363
+ resolve(options.initial);
1364
+ return;
1365
+ }
1366
+ resolve(currentValue);
1367
+ }
1368
+ });
1369
+ });
1370
+ } catch {
1371
+ await destroyRenderer();
1372
+ return null;
1373
+ }
1374
+ };
1022
1375
  const getConfirmation = async () => {
1023
- const response = await prompts(holdToken);
1024
- return response.value;
1376
+ return askConfirm(holdToken.message, holdToken.initial);
1025
1377
  };
1026
1378
  const getDryRunChoice = async () => {
1027
- const response = await prompts(dryRunToggle);
1028
- return Boolean(response.dryRun);
1379
+ return askConfirm(dryRunToggle.message, dryRunToggle.initial);
1029
1380
  };
1030
1381
  const log$3 = console.log;
1031
1382
  const generateSampleJson = async () => {
@@ -1347,8 +1698,13 @@ const importLabelsFromFile = async (configs2, filePath, dryRun = false) => {
1347
1698
  return summary;
1348
1699
  };
1349
1700
  const getTargetLabel = async () => {
1350
- const response = await prompts(deleteLabel$1);
1351
- return [response.name];
1701
+ while (true) {
1702
+ const name = (await askText(deleteLabel$1.message)).trim();
1703
+ if (name.length > 0) {
1704
+ return [name];
1705
+ }
1706
+ console.log(chalk.yellow("Label name cannot be empty. Please try again."));
1707
+ }
1352
1708
  };
1353
1709
  const GIT_COMMAND_TIMEOUT_MS = 5e3;
1354
1710
  class GitRepositoryDetector {
@@ -1455,14 +1811,14 @@ class GitRepositoryDetector {
1455
1811
  */
1456
1812
  static async getRemoteUrl(gitRoot, remoteName) {
1457
1813
  try {
1458
- const { stdout } = await this.execAsyncInternal(
1814
+ const { stdout: stdout2 } = await this.execAsyncInternal(
1459
1815
  `git remote get-url ${remoteName}`,
1460
1816
  {
1461
1817
  cwd: gitRoot,
1462
1818
  timeout: GIT_COMMAND_TIMEOUT_MS
1463
1819
  }
1464
1820
  );
1465
- return stdout.trim() || null;
1821
+ return stdout2.trim() || null;
1466
1822
  } catch {
1467
1823
  return null;
1468
1824
  }
@@ -1532,12 +1888,12 @@ class GitRepositoryDetector {
1532
1888
  */
1533
1889
  static async getAllRemotes(gitRoot) {
1534
1890
  try {
1535
- const { stdout } = await this.execAsyncInternal("git remote", {
1891
+ const { stdout: stdout2 } = await this.execAsyncInternal("git remote", {
1536
1892
  cwd: gitRoot,
1537
1893
  timeout: GIT_COMMAND_TIMEOUT_MS
1538
1894
  });
1539
1895
  return {
1540
- remotes: stdout.trim().split("\n").filter((remote) => remote.length > 0)
1896
+ remotes: stdout2.trim().split("\n").filter((remote) => remote.length > 0)
1541
1897
  };
1542
1898
  } catch (err) {
1543
1899
  const error = err;
@@ -1551,6 +1907,21 @@ class GitRepositoryDetector {
1551
1907
  }
1552
1908
  }
1553
1909
  }
1910
+ const askRequiredValue = async (ask, fieldName) => {
1911
+ while (true) {
1912
+ const rawValue = await ask();
1913
+ if (rawValue === null) {
1914
+ throw new Error(`${fieldName} input was canceled by user.`);
1915
+ }
1916
+ const value = rawValue.trim();
1917
+ if (value.length > 0) {
1918
+ return value;
1919
+ }
1920
+ console.log(
1921
+ chalk.yellow(`⚠️ ${fieldName} cannot be empty. Please try again.`)
1922
+ );
1923
+ }
1924
+ };
1554
1925
  const getGitHubConfigs = async () => {
1555
1926
  const configManager2 = new ConfigManager();
1556
1927
  let validationResult = {
@@ -1615,91 +1986,108 @@ const getGitHubConfigs = async () => {
1615
1986
  console.log(chalk.gray(` Error: ${error.message}`));
1616
1987
  }
1617
1988
  }
1618
- const repoResponse = await prompts([
1619
- {
1620
- type: "text",
1621
- name: "repo",
1622
- message: "Please type your target repo name"
1623
- }
1624
- ]);
1989
+ const repoPrompt2 = githubConfigs.find((prompt) => prompt.name === "repo");
1990
+ const repo2 = await askRequiredValue(
1991
+ () => askText(repoPrompt2?.message ?? "Please type your target repo name"),
1992
+ "Repository name"
1993
+ );
1625
1994
  const octokit2 = new Octokit({
1626
1995
  auth: validationResult.config.token
1627
1996
  });
1628
1997
  return {
1629
1998
  octokit: octokit2,
1630
1999
  owner: validationResult.config.owner,
1631
- repo: repoResponse.repo,
2000
+ repo: repo2,
1632
2001
  fromSavedConfig: true,
1633
2002
  autoDetected: false,
1634
2003
  detectionMethod: "manual"
1635
2004
  };
1636
2005
  }
1637
- const promptConfig = [...githubConfigs];
1638
- if (validationResult.preservedData?.owner) {
1639
- const ownerPromptIndex = promptConfig.findIndex(
1640
- (prompt) => prompt.name === "owner"
1641
- );
1642
- if (ownerPromptIndex !== -1) {
1643
- promptConfig[ownerPromptIndex] = {
1644
- ...promptConfig[ownerPromptIndex],
1645
- initial: validationResult.preservedData.owner
1646
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1647
- };
2006
+ const tokenPrompt = githubConfigs.find((prompt) => prompt.name === "octokit");
2007
+ const ownerPrompt = githubConfigs.find((prompt) => prompt.name === "owner");
2008
+ const repoPrompt = githubConfigs.find((prompt) => prompt.name === "repo");
2009
+ const octokitToken = await askRequiredValue(
2010
+ () => askPassword(tokenPrompt?.message ?? "Please type your personal token"),
2011
+ "Personal token"
2012
+ );
2013
+ const owner = await askRequiredValue(
2014
+ () => askText(ownerPrompt?.message ?? "Please type your GitHub account", {
2015
+ initial: validationResult.preservedData?.owner
2016
+ }),
2017
+ "GitHub account"
2018
+ );
2019
+ const repo = await askRequiredValue(
2020
+ () => askText(repoPrompt?.message ?? "Please type your target repo name"),
2021
+ "Repository name"
2022
+ );
2023
+ try {
2024
+ await configManager2.saveConfig({
2025
+ token: octokitToken,
2026
+ owner,
2027
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
2028
+ });
2029
+ if (validationResult.preservedData?.owner && validationResult.preservedData.owner !== owner) {
2030
+ console.log("✓ Configuration updated with new credentials");
2031
+ } else {
2032
+ console.log("✓ Configuration saved successfully");
1648
2033
  }
1649
- }
1650
- const response = await prompts(promptConfig);
1651
- if (response.octokit && response.owner) {
1652
- try {
1653
- await configManager2.saveConfig({
1654
- token: response.octokit,
1655
- owner: response.owner,
1656
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1657
- });
1658
- if (validationResult.preservedData?.owner && validationResult.preservedData.owner !== response.owner) {
1659
- console.log("✓ Configuration updated with new credentials");
1660
- } else {
1661
- console.log("✓ Configuration saved successfully");
1662
- }
1663
- } catch (error) {
1664
- if (error instanceof ConfigError) {
1665
- console.error(`❌ ${ConfigManager.getErrorMessage(error)}`);
1666
- if (!ConfigManager.isRecoverableError(error)) {
1667
- console.error(
1668
- " This may affect future sessions. Please resolve the issue or contact support."
1669
- );
1670
- }
1671
- } else {
1672
- console.warn(
1673
- "⚠️ Failed to save configuration:",
1674
- error instanceof Error ? error.message : "Unknown error"
2034
+ } catch (error) {
2035
+ if (error instanceof ConfigError) {
2036
+ console.error(`❌ ${ConfigManager.getErrorMessage(error)}`);
2037
+ if (!ConfigManager.isRecoverableError(error)) {
2038
+ console.error(
2039
+ " This may affect future sessions. Please resolve the issue or contact support."
1675
2040
  );
1676
2041
  }
2042
+ } else {
2043
+ console.warn(
2044
+ "⚠️ Failed to save configuration:",
2045
+ error instanceof Error ? error.message : "Unknown error"
2046
+ );
1677
2047
  }
1678
2048
  }
1679
2049
  const octokit = new Octokit({
1680
- auth: response.octokit
2050
+ auth: octokitToken
1681
2051
  });
1682
2052
  return {
1683
2053
  octokit,
1684
- owner: response.owner,
1685
- repo: response.repo,
2054
+ owner,
2055
+ repo,
1686
2056
  fromSavedConfig: false,
1687
2057
  autoDetected: false,
1688
2058
  detectionMethod: "manual"
1689
2059
  };
1690
2060
  };
1691
2061
  const getLabelFilePath = async () => {
1692
- const response = await prompts(labelFilePath);
1693
- return response.filePath;
2062
+ while (true) {
2063
+ const filePath = (await askText(labelFilePath.message)).trim();
2064
+ if (filePath.length > 0) {
2065
+ return filePath;
2066
+ }
2067
+ console.log("File path cannot be empty. Please try again.");
2068
+ }
2069
+ };
2070
+ const getPromptMessage = (field, fallback) => {
2071
+ return newLabel.find((prompt) => prompt.name === field)?.message ?? fallback;
1694
2072
  };
1695
2073
  const getNewLabel = async () => {
1696
- const response = await prompts(newLabel);
1697
- return response;
2074
+ const name = await askText(
2075
+ getPromptMessage("name", "Please type new label name")
2076
+ );
2077
+ const color = await askText(
2078
+ getPromptMessage("color", 'Please type label color without "#" ')
2079
+ );
2080
+ const description = await askText(
2081
+ getPromptMessage("description", "Please type label description")
2082
+ );
2083
+ return {
2084
+ name,
2085
+ color,
2086
+ description
2087
+ };
1698
2088
  };
1699
2089
  const selectAction = async () => {
1700
- const response = await prompts(actionSelector);
1701
- const { action } = response;
1702
- return action[0] !== void 0 ? action[0] : 99;
2090
+ return askSelect(actionSelector.message, actionSelector.choices);
1703
2091
  };
1704
2092
  const log = console.log;
1705
2093
  let firstStart = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyouji",
3
- "version": "0.0.17",
3
+ "version": "0.1.1",
4
4
  "description": "Hyouji (表示) — A command-line tool for organizing and displaying GitHub labels with clarity and harmony.",
5
5
  "main": "dist/index.js",
6
6
  "author": "koji <baxin1919@gmail.com>",
@@ -24,7 +24,8 @@
24
24
  "表示",
25
25
  "Hyouji(表示)",
26
26
  "JSON",
27
- "yaml"
27
+ "yaml",
28
+ "tui"
28
29
  ],
29
30
  "scripts": {
30
31
  "start": "node dist/index.js",
@@ -42,23 +43,19 @@
42
43
  "test:auto-detection": "node tests/integration/auto-detection/integration-flow.cjs",
43
44
  "test:verification": "node tests/scripts/verification/run-all.cjs",
44
45
  "test:all-custom": "npm run test:error-handling && npm run test:config && npm run test:integration && npm run test:verification",
45
- "check-cli": "run-s test diff-integration-tests check-integration-tests",
46
- "check-integration-tests": "run-s check-integration-test:*",
47
46
  "diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
48
47
  "watch:build": "tsc -p tsconfig.json -w",
49
- "cov:send": "run-s cov:lcov && codecov",
50
48
  "version": "standard-version",
51
- "reset-hard": "git clean -dfx && git reset --hard && yarn",
52
- "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
49
+ "reset-hard": "git clean -dfx && git reset --hard && bun install"
53
50
  },
54
51
  "engines": {
55
52
  "node": ">=22.22.0"
56
53
  },
57
54
  "dependencies": {
58
55
  "@octokit/core": "^7.0.6",
56
+ "@opentui/core": "0.1.80",
59
57
  "chalk": "^5.6.2",
60
58
  "oh-my-logo": "^0.3.2",
61
- "prompts": "^2.4.2",
62
59
  "yaml": "^2.8.1"
63
60
  },
64
61
  "devDependencies": {
@@ -66,6 +63,8 @@
66
63
  "@types/node": "^24.10.0",
67
64
  "@vitest/coverage-v8": "^4.0.17",
68
65
  "@vitest/ui": "^4.0.17",
66
+ "knip": "^5.85.0",
67
+ "lefthook": "^2.1.1",
69
68
  "standard-version": "^9.5.0",
70
69
  "typescript": "^5.9.3",
71
70
  "vite": "^7.3.1",