hyouji 0.0.6 → 0.0.8

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 -0
  2. package/dist/index.js +369 -88
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -77,6 +77,9 @@ hyouji
77
77
  On your first run, you'll be prompted to enter:
78
78
 
79
79
  - **GitHub Personal Token** - Generate one [here](https://github.com/settings/tokens) with `repo` scope
80
+ <img width="792" height="564" alt="github_token" src="https://github.com/user-attachments/assets/e460738f-833a-4158-a8ba-61752beaad72" />
81
+
82
+
80
83
  - **GitHub Username** - Your GitHub account name
81
84
 
82
85
  These credentials will be securely saved and reused for future sessions.
package/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { Octokit } from "@octokit/core";
3
2
  import chalk from "chalk";
4
- import prompts from "prompts";
5
3
  import { renderFilled } from "oh-my-logo";
6
4
  import * as fs from "fs";
7
5
  import { promises, existsSync } from "fs";
8
6
  import { homedir } from "os";
9
- import { join } from "path";
7
+ import { join, dirname } from "path";
10
8
  import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
9
+ import prompts from "prompts";
10
+ import { Octokit } from "@octokit/core";
11
+ import { exec } from "child_process";
12
+ import { promisify } from "util";
11
13
  const githubConfigs = [
12
14
  {
13
15
  type: "password",
@@ -62,8 +64,9 @@ const actionSelector = {
62
64
  { title: "delete a label", value: 2 },
63
65
  { title: "delete all labels", value: 3 },
64
66
  { title: "import JSON", value: 4 },
65
- { title: "Display your settings", value: 5 },
66
- { title: "exit", value: 6 }
67
+ { title: "Generate sample JSON", value: 5 },
68
+ { title: "Display your settings", value: 6 },
69
+ { title: "exit", value: 7 }
67
70
  ]
68
71
  };
69
72
  const holdToken = {
@@ -72,6 +75,23 @@ const holdToken = {
72
75
  message: "Do you have a personal token?",
73
76
  initial: true
74
77
  };
78
+ const sampleData = [
79
+ {
80
+ name: "Type: Bug Fix",
81
+ color: "FF8A65",
82
+ description: "Fix features that are not working"
83
+ },
84
+ {
85
+ name: "Type: Enhancement",
86
+ color: "64B5F7",
87
+ description: "Add new features"
88
+ },
89
+ {
90
+ name: "Type: Improvement",
91
+ color: "4DB6AC",
92
+ description: "Improve existing functionality"
93
+ }
94
+ ];
75
95
  const labels = (
76
96
  // the following labels are based on this post
77
97
  // https://qiita.com/willow-micro/items/51eeb3efe5b4192a4abd
@@ -245,7 +265,7 @@ Thank you!
245
265
  };
246
266
  const extraGuideText = `If you don't see action selector, please hit space key.`;
247
267
  const linkToPersonalToken = "https://github.com/settings/tokens";
248
- const log$2 = console.log;
268
+ const log$3 = console.log;
249
269
  const createLabel = async (configs2, label) => {
250
270
  const resp = await configs2.octokit.request(
251
271
  "POST /repos/{owner}/{repo}/labels",
@@ -260,16 +280,16 @@ const createLabel = async (configs2, label) => {
260
280
  const status = resp.status;
261
281
  switch (status) {
262
282
  case 201:
263
- log$2(chalk.green(`${resp.status}: Created ${label.name}`));
283
+ log$3(chalk.green(`${resp.status}: Created ${label.name}`));
264
284
  break;
265
285
  case 404:
266
- log$2(chalk.red(`${resp.status}: Resource not found`));
286
+ log$3(chalk.red(`${resp.status}: Resource not found`));
267
287
  break;
268
288
  case 422:
269
- log$2(chalk.red(`${resp.status}: Validation failed`));
289
+ log$3(chalk.red(`${resp.status}: Validation failed`));
270
290
  break;
271
291
  default:
272
- log$2(chalk.yellow(`${resp.status}: Something wrong`));
292
+ log$3(chalk.yellow(`${resp.status}: Something wrong`));
273
293
  break;
274
294
  }
275
295
  };
@@ -277,8 +297,8 @@ const createLabels = async (configs2) => {
277
297
  labels.forEach(async (label) => {
278
298
  createLabel(configs2, label);
279
299
  });
280
- log$2("Created all labels");
281
- log$2(chalk.bgBlueBright(extraGuideText));
300
+ log$3("Created all labels");
301
+ log$3(chalk.bgBlueBright(extraGuideText));
282
302
  };
283
303
  const deleteLabel = async (configs2, labelNames) => {
284
304
  for (const labelName of labelNames) {
@@ -292,15 +312,15 @@ const deleteLabel = async (configs2, labelNames) => {
292
312
  }
293
313
  );
294
314
  if (resp.status === 204) {
295
- log$2(chalk.green(`${resp.status}: Deleted ${labelName}`));
315
+ log$3(chalk.green(`${resp.status}: Deleted ${labelName}`));
296
316
  } else {
297
- log$2(chalk.yellow(`${resp.status}: Something wrong with ${labelName}`));
317
+ log$3(chalk.yellow(`${resp.status}: Something wrong with ${labelName}`));
298
318
  }
299
319
  } catch (error) {
300
320
  if (error && typeof error === "object" && "status" in error && error.status === 404) {
301
- log$2(chalk.red(`404: Label "${labelName}" not found`));
321
+ log$3(chalk.red(`404: Label "${labelName}" not found`));
302
322
  } else {
303
- log$2(
323
+ log$3(
304
324
  chalk.red(
305
325
  `Error deleting label "${labelName}": ${error instanceof Error ? error.message : "Unknown error"}`
306
326
  )
@@ -321,17 +341,17 @@ const getLabels = async (configs2) => {
321
341
  const names = await resp.data.map((label) => label.name);
322
342
  return names;
323
343
  } else {
324
- log$2(chalk.red("something wrong"));
344
+ log$3(chalk.red("something wrong"));
325
345
  return [];
326
346
  }
327
347
  };
328
348
  const deleteLabels = async (configs2) => {
329
349
  const names = await getLabels(configs2);
330
350
  if (names.length === 0) {
331
- log$2(chalk.yellow("No labels found to delete"));
351
+ log$3(chalk.yellow("No labels found to delete"));
332
352
  return;
333
353
  }
334
- log$2(chalk.blue(`Deleting ${names.length} labels...`));
354
+ log$3(chalk.blue(`Deleting ${names.length} labels...`));
335
355
  for (const name of names) {
336
356
  try {
337
357
  const resp = await configs2.octokit.request(
@@ -343,15 +363,15 @@ const deleteLabels = async (configs2) => {
343
363
  }
344
364
  );
345
365
  if (resp.status === 204) {
346
- log$2(chalk.green(`${resp.status}: Deleted ${name}`));
366
+ log$3(chalk.green(`${resp.status}: Deleted ${name}`));
347
367
  } else {
348
- log$2(chalk.yellow(`${resp.status}: Something wrong with ${name}`));
368
+ log$3(chalk.yellow(`${resp.status}: Something wrong with ${name}`));
349
369
  }
350
370
  } catch (error) {
351
371
  if (error && typeof error === "object" && "status" in error && error.status === 404) {
352
- log$2(chalk.red(`404: Label "${name}" not found`));
372
+ log$3(chalk.red(`404: Label "${name}" not found`));
353
373
  } else {
354
- log$2(
374
+ log$3(
355
375
  chalk.red(
356
376
  `Error deleting label "${name}": ${error instanceof Error ? error.message : "Unknown error"}`
357
377
  )
@@ -359,8 +379,8 @@ const deleteLabels = async (configs2) => {
359
379
  }
360
380
  }
361
381
  }
362
- log$2(chalk.blue("Finished deleting labels"));
363
- log$2(chalk.bgBlueBright(extraGuideText));
382
+ log$3(chalk.blue("Finished deleting labels"));
383
+ log$3(chalk.bgBlueBright(extraGuideText));
364
384
  };
365
385
  const _CryptoUtils = class _CryptoUtils {
366
386
  /**
@@ -954,6 +974,53 @@ const getConfirmation = async () => {
954
974
  const response = await prompts(holdToken);
955
975
  return response.value;
956
976
  };
977
+ const log$2 = console.log;
978
+ const generateSampleJson = async () => {
979
+ try {
980
+ const outputPath = "./hyouji.json";
981
+ const jsonContent = JSON.stringify(sampleData, null, 2);
982
+ log$2(chalk.blue("Generating sample JSON file..."));
983
+ fs.writeFileSync(outputPath, jsonContent, "utf8");
984
+ log$2(
985
+ chalk.green(
986
+ "✅ Sample JSON file generated successfully at ./hyouji.json"
987
+ )
988
+ );
989
+ } catch (error) {
990
+ if (error instanceof Error) {
991
+ const nodeError = error;
992
+ if (nodeError.code === "EACCES") {
993
+ log$2(
994
+ chalk.red(
995
+ "❌ Error generating sample JSON file: Permission denied. Please check write permissions for the current directory."
996
+ )
997
+ );
998
+ } else if (nodeError.code === "ENOSPC") {
999
+ log$2(
1000
+ chalk.red(
1001
+ "❌ Error generating sample JSON file: Insufficient disk space."
1002
+ )
1003
+ );
1004
+ } else if (nodeError.code === "EROFS") {
1005
+ log$2(
1006
+ chalk.red(
1007
+ "❌ Error generating sample JSON file: Read-only file system."
1008
+ )
1009
+ );
1010
+ } else {
1011
+ log$2(
1012
+ chalk.red(`❌ Error generating sample JSON file: ${error.message}`)
1013
+ );
1014
+ }
1015
+ } else {
1016
+ log$2(
1017
+ chalk.red(
1018
+ "❌ An unexpected error occurred while generating the sample JSON file"
1019
+ )
1020
+ );
1021
+ }
1022
+ }
1023
+ };
957
1024
  const log$1 = console.log;
958
1025
  const importLabelsFromJson = async (configs2, filePath) => {
959
1026
  try {
@@ -1109,6 +1176,177 @@ const getTargetLabel = async () => {
1109
1176
  const response = await prompts(deleteLabel$1);
1110
1177
  return [response.name];
1111
1178
  };
1179
+ const execAsync = promisify(exec);
1180
+ const GIT_COMMAND_TIMEOUT_MS = 5e3;
1181
+ class GitRepositoryDetector {
1182
+ /**
1183
+ * Detects Git repository information from the current working directory
1184
+ * @param cwd - Current working directory (defaults to process.cwd())
1185
+ * @returns Promise<GitDetectionResult>
1186
+ */
1187
+ static async detectRepository(cwd) {
1188
+ const workingDir = cwd || process.cwd();
1189
+ try {
1190
+ const gitRoot = await this.findGitRoot(workingDir);
1191
+ if (!gitRoot) {
1192
+ return {
1193
+ isGitRepository: false,
1194
+ error: "Not a Git repository"
1195
+ };
1196
+ }
1197
+ const remotes = await this.getAllRemotes(gitRoot);
1198
+ if (remotes.length === 0) {
1199
+ return {
1200
+ isGitRepository: true,
1201
+ error: "No remotes configured"
1202
+ };
1203
+ }
1204
+ let remoteUrl = null;
1205
+ let detectionMethod = "origin";
1206
+ if (remotes.includes("origin")) {
1207
+ remoteUrl = await this.getRemoteUrl(gitRoot, "origin");
1208
+ }
1209
+ if (!remoteUrl && remotes.length > 0) {
1210
+ remoteUrl = await this.getRemoteUrl(gitRoot, remotes[0]);
1211
+ detectionMethod = "first-remote";
1212
+ }
1213
+ if (!remoteUrl) {
1214
+ return {
1215
+ isGitRepository: true,
1216
+ error: "Could not retrieve remote URL"
1217
+ };
1218
+ }
1219
+ const parsedUrl = this.parseGitUrl(remoteUrl);
1220
+ if (!parsedUrl) {
1221
+ return {
1222
+ isGitRepository: true,
1223
+ error: "Could not parse remote URL"
1224
+ };
1225
+ }
1226
+ return {
1227
+ isGitRepository: true,
1228
+ repositoryInfo: {
1229
+ owner: parsedUrl.owner,
1230
+ repo: parsedUrl.repo,
1231
+ remoteUrl,
1232
+ detectionMethod
1233
+ }
1234
+ };
1235
+ } catch (err) {
1236
+ return {
1237
+ isGitRepository: false,
1238
+ error: err instanceof Error ? err.message : "Unknown error occurred"
1239
+ };
1240
+ }
1241
+ }
1242
+ /**
1243
+ * Finds the Git root directory by traversing up the directory tree
1244
+ * @param startPath - Starting directory path
1245
+ * @returns Promise<string | null> - Git root path or null if not found
1246
+ */
1247
+ static async findGitRoot(startPath) {
1248
+ let currentPath = startPath;
1249
+ while (currentPath !== dirname(currentPath)) {
1250
+ const gitPath = join(currentPath, ".git");
1251
+ if (existsSync(gitPath)) {
1252
+ return currentPath;
1253
+ }
1254
+ currentPath = dirname(currentPath);
1255
+ }
1256
+ return null;
1257
+ }
1258
+ /**
1259
+ * Gets the URL for a specific Git remote
1260
+ * @param gitRoot - Git repository root directory
1261
+ * @param remoteName - Name of the remote (e.g., 'origin')
1262
+ * @returns Promise<string | null> - Remote URL or null if not found
1263
+ */
1264
+ static async getRemoteUrl(gitRoot, remoteName) {
1265
+ try {
1266
+ const { stdout } = await execAsync(`git remote get-url ${remoteName}`, {
1267
+ cwd: gitRoot,
1268
+ timeout: GIT_COMMAND_TIMEOUT_MS
1269
+ });
1270
+ return stdout.trim() || null;
1271
+ } catch {
1272
+ return null;
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Parses a Git URL to extract owner and repository name
1277
+ * @param url - Git remote URL
1278
+ * @returns Object with owner and repo or null if parsing fails
1279
+ */
1280
+ static parseGitUrl(url) {
1281
+ if (!url || typeof url !== "string" || url.trim().length === 0) {
1282
+ return null;
1283
+ }
1284
+ const trimmedUrl = url.trim();
1285
+ try {
1286
+ const sshMatch = trimmedUrl.match(
1287
+ /^git@github\.com:([^/\s:]+)\/([^/\s:]+?)(?:\.git)?$/
1288
+ );
1289
+ if (sshMatch) {
1290
+ const owner = sshMatch[1];
1291
+ const repo = sshMatch[2];
1292
+ if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
1293
+ return { owner, repo };
1294
+ }
1295
+ }
1296
+ const httpsMatch = trimmedUrl.match(
1297
+ /^https:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
1298
+ );
1299
+ if (httpsMatch) {
1300
+ const owner = httpsMatch[1];
1301
+ const repo = httpsMatch[2];
1302
+ if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
1303
+ return { owner, repo };
1304
+ }
1305
+ }
1306
+ const httpMatch = trimmedUrl.match(
1307
+ /^http:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/)?$/
1308
+ );
1309
+ if (httpMatch) {
1310
+ const owner = httpMatch[1];
1311
+ const repo = httpMatch[2];
1312
+ if (this.isValidGitHubIdentifier(owner) && this.isValidGitHubIdentifier(repo)) {
1313
+ return { owner, repo };
1314
+ }
1315
+ }
1316
+ } catch {
1317
+ return null;
1318
+ }
1319
+ return null;
1320
+ }
1321
+ /**
1322
+ * Validates if a string is a valid GitHub identifier (username or repository name)
1323
+ * @param identifier - The identifier to validate
1324
+ * @returns boolean - True if valid, false otherwise
1325
+ */
1326
+ static isValidGitHubIdentifier(identifier) {
1327
+ if (!identifier || typeof identifier !== "string") {
1328
+ return false;
1329
+ }
1330
+ const GITHUB_IDENTIFIER_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
1331
+ return identifier.length >= 1 && identifier.length <= 39 && GITHUB_IDENTIFIER_REGEX.test(identifier) && !identifier.includes("--");
1332
+ }
1333
+ /**
1334
+ * Gets all configured Git remotes
1335
+ * @param gitRoot - Git repository root directory
1336
+ * @returns Promise<string[]> - Array of remote names
1337
+ */
1338
+ static async getAllRemotes(gitRoot) {
1339
+ try {
1340
+ const { stdout } = await execAsync("git remote", {
1341
+ cwd: gitRoot,
1342
+ timeout: GIT_COMMAND_TIMEOUT_MS
1343
+ });
1344
+ return stdout.trim().split("\n").filter((remote) => remote.length > 0);
1345
+ } catch {
1346
+ return [];
1347
+ }
1348
+ }
1349
+ }
1112
1350
  const getGitHubConfigs = async () => {
1113
1351
  var _a, _b;
1114
1352
  const configManager2 = new ConfigManager();
@@ -1130,6 +1368,50 @@ const getGitHubConfigs = async () => {
1130
1368
  };
1131
1369
  }
1132
1370
  if (validationResult.config && !validationResult.shouldPromptForCredentials) {
1371
+ try {
1372
+ const detectionResult = await GitRepositoryDetector.detectRepository();
1373
+ if (detectionResult.isGitRepository && detectionResult.repositoryInfo) {
1374
+ console.log(
1375
+ chalk.green(
1376
+ `✓ Detected repository: ${detectionResult.repositoryInfo.owner}/${detectionResult.repositoryInfo.repo}`
1377
+ )
1378
+ );
1379
+ console.log(
1380
+ chalk.gray(
1381
+ ` Detection method: ${detectionResult.repositoryInfo.detectionMethod === "origin" ? "origin remote" : "first available remote"}`
1382
+ )
1383
+ );
1384
+ const octokit3 = new Octokit({
1385
+ auth: validationResult.config.token
1386
+ });
1387
+ return {
1388
+ octokit: octokit3,
1389
+ owner: detectionResult.repositoryInfo.owner,
1390
+ repo: detectionResult.repositoryInfo.repo,
1391
+ fromSavedConfig: true,
1392
+ autoDetected: true,
1393
+ detectionMethod: detectionResult.repositoryInfo.detectionMethod
1394
+ };
1395
+ } else {
1396
+ if (detectionResult.error) {
1397
+ console.log(
1398
+ chalk.yellow(
1399
+ `⚠️ Repository auto-detection failed: ${detectionResult.error}`
1400
+ )
1401
+ );
1402
+ }
1403
+ console.log(chalk.gray(" Falling back to manual input..."));
1404
+ }
1405
+ } catch (error) {
1406
+ console.log(
1407
+ chalk.yellow(
1408
+ "⚠️ Repository auto-detection failed, falling back to manual input"
1409
+ )
1410
+ );
1411
+ if (error instanceof Error) {
1412
+ console.log(chalk.gray(` Error: ${error.message}`));
1413
+ }
1414
+ }
1133
1415
  const repoResponse = await prompts([
1134
1416
  {
1135
1417
  type: "text",
@@ -1144,7 +1426,9 @@ const getGitHubConfigs = async () => {
1144
1426
  octokit: octokit2,
1145
1427
  owner: validationResult.config.owner,
1146
1428
  repo: repoResponse.repo,
1147
- fromSavedConfig: true
1429
+ fromSavedConfig: true,
1430
+ autoDetected: false,
1431
+ detectionMethod: "manual"
1148
1432
  };
1149
1433
  }
1150
1434
  const promptConfig = [...githubConfigs];
@@ -1196,7 +1480,9 @@ const getGitHubConfigs = async () => {
1196
1480
  octokit,
1197
1481
  owner: response.owner,
1198
1482
  repo: response.repo,
1199
- fromSavedConfig: false
1483
+ fromSavedConfig: false,
1484
+ autoDetected: false,
1485
+ detectionMethod: "manual"
1200
1486
  };
1201
1487
  };
1202
1488
  const getJsonFilePath = async () => {
@@ -1215,33 +1501,6 @@ const selectAction = async () => {
1215
1501
  const log = console.log;
1216
1502
  let firstStart = true;
1217
1503
  const configManager = new ConfigManager();
1218
- const setupConfigs = async () => {
1219
- console.log(initialText);
1220
- if (firstStart) {
1221
- await configManager.migrateToEncrypted();
1222
- }
1223
- const config = await getGitHubConfigs();
1224
- if (!config.octokit || !config.owner || !config.repo) {
1225
- throw new Error("Invalid configuration: missing required fields");
1226
- }
1227
- try {
1228
- await config.octokit.request("GET /user");
1229
- } catch (error) {
1230
- if (config.fromSavedConfig) {
1231
- console.log(
1232
- chalk.yellow(
1233
- "Saved credentials are invalid. Please provide new credentials."
1234
- )
1235
- );
1236
- await configManager.clearConfig();
1237
- return setupConfigs();
1238
- }
1239
- throw new Error(
1240
- `GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
1241
- );
1242
- }
1243
- return config;
1244
- };
1245
1504
  const displaySettings = async () => {
1246
1505
  log(chalk.cyan("\n=== Current Settings ==="));
1247
1506
  const configPath = configManager.getConfigPath();
@@ -1318,47 +1577,56 @@ const initializeConfigs = async () => {
1318
1577
  console.warn("Failed to display ASCII art, continuing...");
1319
1578
  console.error("Error:", error);
1320
1579
  }
1321
- if (hasValidConfig) {
1322
- try {
1323
- const existingConfig = await configManager.loadValidatedConfig();
1324
- if (existingConfig && existingConfig.config) {
1325
- const repoResponse = await prompts([
1326
- {
1327
- type: "text",
1328
- name: "repo",
1329
- message: "Please type your target repo name"
1330
- }
1331
- ]);
1332
- const config = {
1333
- octokit: new Octokit({ auth: existingConfig.config.token }),
1334
- owner: existingConfig.config.owner,
1335
- repo: repoResponse.repo,
1336
- fromSavedConfig: true
1337
- };
1338
- log(chalk.green(`Using saved configuration for ${config.owner}`));
1339
- return config;
1340
- } else {
1341
- return await setupConfigs();
1342
- }
1343
- } catch (error) {
1344
- console.error("Error:", error);
1345
- return await setupConfigs();
1580
+ try {
1581
+ console.log(initialText);
1582
+ if (firstStart) {
1583
+ await configManager.migrateToEncrypted();
1584
+ }
1585
+ const config = await getGitHubConfigs();
1586
+ if (!config.octokit || !config.owner || !config.repo) {
1587
+ throw new Error("Invalid configuration: missing required fields");
1346
1588
  }
1347
- } else {
1348
1589
  try {
1349
- const config = await setupConfigs();
1590
+ await config.octokit.request("GET /user");
1591
+ } catch (error) {
1350
1592
  if (config.fromSavedConfig) {
1351
- log(chalk.green(`Using saved configuration for ${config.owner}`));
1593
+ console.log(
1594
+ chalk.yellow(
1595
+ "Saved credentials are invalid. Please provide new credentials."
1596
+ )
1597
+ );
1598
+ await configManager.clearConfig();
1599
+ return initializeConfigs();
1352
1600
  }
1353
- return config;
1354
- } catch (error) {
1601
+ throw new Error(
1602
+ `GitHub API authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
1603
+ );
1604
+ }
1605
+ if (config.fromSavedConfig) {
1606
+ log(chalk.green(`✓ Using saved configuration for ${config.owner}`));
1607
+ }
1608
+ if (config.autoDetected) {
1355
1609
  log(
1356
- chalk.red(
1357
- `Configuration error: ${error instanceof Error ? error.message : "Unknown error"}`
1610
+ chalk.green(
1611
+ `✓ Repository auto-detected: ${config.owner}/${config.repo}`
1358
1612
  )
1359
1613
  );
1360
- return null;
1614
+ const detectionMethodText = config.detectionMethod === "origin" ? "origin remote" : config.detectionMethod === "first-remote" ? "first available remote" : "manual input";
1615
+ log(chalk.gray(` Detection method: ${detectionMethodText}`));
1616
+ } else if (config.detectionMethod === "manual") {
1617
+ log(
1618
+ chalk.blue(`✓ Repository configured: ${config.owner}/${config.repo}`)
1619
+ );
1620
+ log(chalk.gray(` Input method: manual`));
1361
1621
  }
1622
+ return config;
1623
+ } catch (error) {
1624
+ log(
1625
+ chalk.red(
1626
+ `Configuration error: ${error instanceof Error ? error.message : "Unknown error"}`
1627
+ )
1628
+ );
1629
+ return null;
1362
1630
  }
1363
1631
  };
1364
1632
  const main = async () => {
@@ -1414,11 +1682,24 @@ const main = async () => {
1414
1682
  break;
1415
1683
  }
1416
1684
  case 5: {
1417
- await displaySettings();
1685
+ try {
1686
+ await generateSampleJson();
1687
+ } catch (error) {
1688
+ log(
1689
+ chalk.red(
1690
+ `Error generating sample JSON: ${error instanceof Error ? error.message : "Unknown error"}`
1691
+ )
1692
+ );
1693
+ }
1418
1694
  firstStart = firstStart && false;
1419
1695
  break;
1420
1696
  }
1421
1697
  case 6: {
1698
+ await displaySettings();
1699
+ firstStart = firstStart && false;
1700
+ break;
1701
+ }
1702
+ case 7: {
1422
1703
  console.log("exit");
1423
1704
  process.exit(0);
1424
1705
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyouji",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "test:error-handling": "node tests/scripts/error-handling/run-all.cjs",
39
39
  "test:config": "node tests/scripts/config/run-all.cjs",
40
40
  "test:integration": "node tests/scripts/run-integration.cjs",
41
+ "test:auto-detection": "node tests/integration/auto-detection/integration-flow.cjs",
41
42
  "test:verification": "node tests/scripts/verification/run-all.cjs",
42
43
  "test:all-custom": "npm run test:error-handling && npm run test:config && npm run test:integration && npm run test:verification",
43
44
  "check-cli": "run-s test diff-integration-tests check-integration-tests",