github-labels-template 0.7.0 → 0.8.0-patch.30e7740

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 (2) hide show
  1. package/dist/index.js +346 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { defineCommand as defineCommand6, runMain } from "citty";
4
+ import { defineCommand as defineCommand8, runMain } from "citty";
5
5
 
6
6
  // src/commands/apply.ts
7
7
  import { defineCommand } from "citty";
@@ -621,21 +621,24 @@ var CATEGORY_NAMES = {
621
621
  resolution: "Resolution",
622
622
  area: "Area"
623
623
  };
624
- function buildSystemPrompt(category, count) {
624
+ function buildSystemPrompt(category, count, attempt = 1) {
625
625
  const categoryTitle = CATEGORY_NAMES[category] ?? category;
626
626
  const existingLabels = labels_default[category] ?? [];
627
627
  const existingList = existingLabels.map((l) => ` - "${l.name}" (${l.color}) — ${l.description}`).join(`
628
628
  `);
629
+ const variationHint = attempt > 1 ? `IMPORTANT: This is attempt #${attempt}. You MUST generate completely different label names, colors, and descriptions than any previous suggestions. Be creative and explore new angles.` : null;
629
630
  return [
630
- `You are a GitHub label generator. Generate exactly ${count} label suggestions for the "${categoryTitle}" category.`,
631
+ `You are a GitHub label generator. The user will describe the kind of label they need. Generate exactly ${count} label suggestions for the "${categoryTitle}" category that DIRECTLY match what the user is asking for.`,
631
632
  "",
633
+ "CRITICAL: Every suggestion MUST be relevant to the user's description. Do NOT generate generic or unrelated labels. Focus on what the user specifically asked for.",
634
+ ...variationHint ? ["", variationHint, ""] : [""],
632
635
  "Each label must follow this exact JSON format:",
633
636
  "[",
634
637
  ` { "name": "label-name", "color": "hex123", "description": "[${categoryTitle}] Description text [scope]" }`,
635
638
  "]",
636
639
  "",
637
640
  "Rules:",
638
- "- name: lowercase, concise (1-3 words), use spaces for multi-word names",
641
+ "- name: lowercase, concise (1-3 words), use spaces for multi-word names, must reflect the user's request",
639
642
  "- color: 6-character hex without #, choose colors that are visually distinct from existing labels",
640
643
  `- description: MUST start with [${categoryTitle}] and end with [issues], [PRs], or [issues, PRs]`,
641
644
  "- Do NOT duplicate any of these existing labels:",
@@ -645,12 +648,17 @@ function buildSystemPrompt(category, count) {
645
648
  ].join(`
646
649
  `);
647
650
  }
648
- function buildUserPrompt(description, refinement) {
651
+ function buildUserPrompt(description, refinement, attempt = 1) {
649
652
  let prompt = `I need a label for: ${description}`;
650
653
  if (refinement) {
651
654
  prompt += `
652
655
 
653
656
  Refinement feedback: ${refinement}`;
657
+ }
658
+ if (attempt > 1) {
659
+ prompt += `
660
+
661
+ Generate different suggestions from previous attempts. This is attempt #${attempt}, so provide fresh and unique alternatives.`;
654
662
  }
655
663
  return prompt;
656
664
  }
@@ -683,13 +691,13 @@ function parseLabelsResponse(text) {
683
691
  });
684
692
  }
685
693
  async function generateLabels(options) {
686
- const { category, description, count = 3, refinement, model } = options;
694
+ const { category, description, count = 3, refinement, model, attempt = 1 } = options;
687
695
  const client = new CopilotClient;
688
696
  await client.start();
689
697
  try {
690
698
  const sessionConfig = {
691
699
  systemMessage: {
692
- content: buildSystemPrompt(category, count)
700
+ content: buildSystemPrompt(category, count, attempt)
693
701
  }
694
702
  };
695
703
  if (model) {
@@ -697,7 +705,7 @@ async function generateLabels(options) {
697
705
  }
698
706
  const session = await client.createSession(sessionConfig);
699
707
  try {
700
- const userPrompt = buildUserPrompt(description, refinement);
708
+ const userPrompt = buildUserPrompt(description, refinement, attempt);
701
709
  const response = await session.sendAndWait({ content: userPrompt });
702
710
  if (!response || !response.data?.content) {
703
711
  throw new Error("No response received from Copilot");
@@ -818,6 +826,7 @@ var generate_default = defineCommand3({
818
826
  });
819
827
  let selectedLabel = null;
820
828
  let refinement;
829
+ let attempt = 1;
821
830
  while (!selectedLabel) {
822
831
  info(refinement ? "Regenerating with your feedback..." : "Generating label suggestions...");
823
832
  let suggestions;
@@ -827,7 +836,8 @@ var generate_default = defineCommand3({
827
836
  description,
828
837
  count: 3,
829
838
  refinement,
830
- model: args.model
839
+ model: args.model,
840
+ attempt
831
841
  });
832
842
  } catch (err) {
833
843
  error(`Failed to generate labels: ${err instanceof Error ? err.message : String(err)}`);
@@ -837,6 +847,7 @@ var generate_default = defineCommand3({
837
847
  });
838
848
  if (retry) {
839
849
  refinement = undefined;
850
+ attempt = 1;
840
851
  continue;
841
852
  }
842
853
  return;
@@ -873,10 +884,12 @@ var generate_default = defineCommand3({
873
884
  message: "What would you like to change?",
874
885
  validate: (value) => value.trim().length > 0 || "Please provide feedback."
875
886
  });
887
+ attempt++;
876
888
  continue;
877
889
  }
878
890
  if (choice === "regenerate") {
879
891
  refinement = undefined;
892
+ attempt++;
880
893
  continue;
881
894
  }
882
895
  const pickIndex = parseInt(choice.replace("pick:", ""), 10);
@@ -1063,13 +1076,171 @@ var list_default = defineCommand5({
1063
1076
  }
1064
1077
  });
1065
1078
 
1079
+ // src/commands/check.ts
1080
+ import { defineCommand as defineCommand6 } from "citty";
1081
+ import pc7 from "picocolors";
1082
+ function statusIcon(status) {
1083
+ if (status === "match")
1084
+ return pc7.green("✔");
1085
+ if (status === "missing")
1086
+ return pc7.red("✘");
1087
+ return pc7.yellow("~");
1088
+ }
1089
+ function statusLabel(status) {
1090
+ switch (status) {
1091
+ case "match":
1092
+ return pc7.dim("match");
1093
+ case "color-mismatch":
1094
+ return pc7.yellow("color mismatch");
1095
+ case "desc-mismatch":
1096
+ return pc7.yellow("description mismatch");
1097
+ case "both-mismatch":
1098
+ return pc7.yellow("color + description mismatch");
1099
+ case "missing":
1100
+ return pc7.red("missing");
1101
+ }
1102
+ }
1103
+ function isFailing(status, strict) {
1104
+ if (status === "missing")
1105
+ return true;
1106
+ if (strict && status !== "match")
1107
+ return true;
1108
+ return false;
1109
+ }
1110
+ var check_default = defineCommand6({
1111
+ meta: {
1112
+ name: "check",
1113
+ description: "Check if a repository is using the Clean Label template"
1114
+ },
1115
+ args: {
1116
+ repo: {
1117
+ type: "string",
1118
+ alias: "r",
1119
+ description: "Target repository (owner/repo). Defaults to current repo."
1120
+ },
1121
+ category: {
1122
+ type: "string",
1123
+ alias: "c",
1124
+ description: 'Check specific category(ies) only. Comma-separated (e.g., --category "type,status")'
1125
+ },
1126
+ strict: {
1127
+ type: "boolean",
1128
+ alias: "s",
1129
+ default: false,
1130
+ description: "Strict mode — also flag labels with mismatched color or description"
1131
+ }
1132
+ },
1133
+ async run({ args }) {
1134
+ if (!await checkGhInstalled()) {
1135
+ error("gh CLI is not installed. Install it from https://cli.github.com");
1136
+ process.exit(1);
1137
+ }
1138
+ if (!await checkGhAuth()) {
1139
+ error("Not authenticated. Run `gh auth login` first.");
1140
+ process.exit(1);
1141
+ }
1142
+ const repo = args.repo || await detectRepo();
1143
+ if (!repo) {
1144
+ error("Could not detect repository. Use --repo <owner/repo> or run inside a git repo.");
1145
+ process.exit(1);
1146
+ }
1147
+ const strict = args.strict ?? false;
1148
+ info(`Target: ${repo}`);
1149
+ if (strict)
1150
+ info("Mode: strict (name + color + description)");
1151
+ const repoLabels = await listLabelsDetailed(repo);
1152
+ const repoMap = new Map(repoLabels.map((l) => [l.name.toLowerCase(), l]));
1153
+ const { entries: templateEntries } = filterLabels(labels_default, {
1154
+ category: args.category
1155
+ });
1156
+ const results = [];
1157
+ for (const [category, categoryLabels] of templateEntries) {
1158
+ for (const tmpl of categoryLabels) {
1159
+ const existing = repoMap.get(tmpl.name.toLowerCase());
1160
+ let status;
1161
+ let repoColor;
1162
+ let repoDesc;
1163
+ if (!existing) {
1164
+ status = "missing";
1165
+ } else {
1166
+ const colorMatch = existing.color.toLowerCase() === tmpl.color.toLowerCase();
1167
+ const descMatch = existing.description.trim() === tmpl.description.trim();
1168
+ if (colorMatch && descMatch) {
1169
+ status = "match";
1170
+ } else if (!colorMatch && !descMatch) {
1171
+ status = "both-mismatch";
1172
+ repoColor = existing.color;
1173
+ repoDesc = existing.description;
1174
+ } else if (!colorMatch) {
1175
+ status = "color-mismatch";
1176
+ repoColor = existing.color;
1177
+ } else {
1178
+ status = "desc-mismatch";
1179
+ repoDesc = existing.description;
1180
+ }
1181
+ }
1182
+ results.push({ template: tmpl, category, status, repoColor, repoDesc });
1183
+ }
1184
+ }
1185
+ const byCategory = new Map;
1186
+ for (const result of results) {
1187
+ if (!byCategory.has(result.category))
1188
+ byCategory.set(result.category, []);
1189
+ byCategory.get(result.category).push(result);
1190
+ }
1191
+ console.log("");
1192
+ for (const [category, catResults] of byCategory) {
1193
+ const catTitle = category.charAt(0).toUpperCase() + category.slice(1);
1194
+ const catFailing = catResults.filter((r) => isFailing(r.status, strict)).length;
1195
+ const catStatus = catFailing > 0 ? pc7.red(`${catFailing} issue${catFailing !== 1 ? "s" : ""}`) : pc7.green("all good");
1196
+ console.log(`${pc7.bold(catTitle)} ${pc7.dim(`(${catResults.length})`)} — ${catStatus}`);
1197
+ for (const result of catResults) {
1198
+ const icon = statusIcon(result.status);
1199
+ const name = result.template.name.padEnd(28);
1200
+ const color = pc7.dim(`#${result.template.color}`);
1201
+ const lbl = statusLabel(result.status);
1202
+ let line = ` ${icon} ${name} ${color} ${lbl}`;
1203
+ if (result.repoColor) {
1204
+ line += pc7.dim(` (repo: #${result.repoColor})`);
1205
+ }
1206
+ if (result.repoDesc) {
1207
+ line += pc7.dim(`
1208
+ repo desc: "${result.repoDesc}"`);
1209
+ }
1210
+ console.log(line);
1211
+ }
1212
+ console.log("");
1213
+ }
1214
+ const matched = results.filter((r) => r.status === "match").length;
1215
+ const missing = results.filter((r) => r.status === "missing").length;
1216
+ const mismatched = results.filter((r) => r.status !== "match" && r.status !== "missing").length;
1217
+ const total = results.length;
1218
+ const failing = results.filter((r) => isFailing(r.status, strict)).length;
1219
+ const mismatchedStr = mismatched > 0 ? pc7.yellow(`, ${mismatched} mismatched`) : "";
1220
+ const missingStr = missing > 0 ? pc7.red(`, ${missing} missing`) : "";
1221
+ const scoreStr = pc7.bold(`${matched}/${total}`);
1222
+ console.log(`${pc7.bold("Result:")} ${scoreStr} labels matched${mismatchedStr}${missingStr}`);
1223
+ if (failing === 0) {
1224
+ const mismatchHint = !strict && mismatched > 0 ? pc7.dim(` (${mismatched} mismatch${mismatched !== 1 ? "es" : ""} — use --strict to enforce)`) : "";
1225
+ console.log(`${pc7.bold("Compatible:")} ${pc7.green("✔ Yes")}${strict ? " (strict)" : ""}${mismatchHint}`);
1226
+ } else {
1227
+ console.log(`${pc7.bold("Compatible:")} ${pc7.red("✘ No")} — run ${pc7.bold("ghlt apply")} to fix`);
1228
+ process.exit(1);
1229
+ }
1230
+ }
1231
+ });
1232
+
1233
+ // src/commands/update.ts
1234
+ import { defineCommand as defineCommand7 } from "citty";
1235
+ import { execSync } from "child_process";
1236
+
1066
1237
  // src/ui/banner.ts
1067
1238
  import figlet from "figlet";
1068
- import pc7 from "picocolors";
1239
+ import pc8 from "picocolors";
1069
1240
  // package.json
1070
1241
  var package_default = {
1071
1242
  name: "github-labels-template",
1072
- version: "0.7.0",
1243
+ version: "0.8.0-patch.30e7740",
1073
1244
  description: "A CLI tool to apply a curated GitHub labels template to any repository using gh CLI.",
1074
1245
  type: "module",
1075
1246
  bin: {
@@ -1133,24 +1304,178 @@ function getVersion() {
1133
1304
  function getAuthor() {
1134
1305
  return package_default.author ?? "unknown";
1135
1306
  }
1307
+ function showUpdateBanner(latestVersion) {
1308
+ const current = getVersion();
1309
+ const inner1 = ` Update available: v${current} → v${latestVersion} `;
1310
+ const inner2 = ` Run ${pc8.bold("ghlt update")} to upgrade. `;
1311
+ const visibleLen = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
1312
+ const width = Math.max(visibleLen(inner1), visibleLen(inner2));
1313
+ const line = "─".repeat(width);
1314
+ console.log(pc8.yellow(`┌${line}┐`));
1315
+ console.log(pc8.yellow("│") + pc8.bold(` Update available: v${current} → v${latestVersion} `) + " ".repeat(width - visibleLen(inner1)) + pc8.yellow("│"));
1316
+ console.log(pc8.yellow("│") + ` Run ${pc8.bold("ghlt update")} to upgrade. ` + " ".repeat(width - visibleLen(inner2)) + pc8.yellow("│"));
1317
+ console.log(pc8.yellow(`└${line}┘`));
1318
+ console.log();
1319
+ }
1136
1320
  function showBanner(minimal = false) {
1137
- console.log(pc7.cyan(`
1321
+ console.log(pc8.cyan(`
1138
1322
  ` + LOGO));
1139
- console.log(` ${pc7.dim("v" + getVersion())} ${pc7.dim("—")} ${pc7.dim("Built by " + getAuthor())}`);
1323
+ console.log(` ${pc8.dim("v" + getVersion())} ${pc8.dim("—")} ${pc8.dim("Built by " + getAuthor())}`);
1140
1324
  if (!minimal) {
1141
- console.log(` ${pc7.dim(package_default.description)}`);
1325
+ console.log(` ${pc8.dim(package_default.description)}`);
1142
1326
  console.log();
1143
- console.log(` ${pc7.yellow("Star")} ${pc7.cyan("https://gh.waren.build/github-labels-template")}`);
1144
- console.log(` ${pc7.green("Contribute")} ${pc7.cyan("https://gh.waren.build/github-labels-template/blob/main/CONTRIBUTING.md")}`);
1145
- console.log(` ${pc7.magenta("Sponsor")} ${pc7.cyan("https://warengonzaga.com/sponsor")}`);
1327
+ console.log(` ${pc8.yellow("Star")} ${pc8.cyan("https://gh.waren.build/github-labels-template")}`);
1328
+ console.log(` ${pc8.green("Contribute")} ${pc8.cyan("https://gh.waren.build/github-labels-template/blob/main/CONTRIBUTING.md")}`);
1329
+ console.log(` ${pc8.magenta("Sponsor")} ${pc8.cyan("https://warengonzaga.com/sponsor")}`);
1146
1330
  }
1147
1331
  console.log();
1148
1332
  }
1149
1333
 
1334
+ // src/utils/updater.ts
1335
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
1336
+ import { join } from "path";
1337
+ import { homedir } from "os";
1338
+ var CACHE_DIR = join(homedir(), ".ghlt");
1339
+ var CACHE_FILE = join(CACHE_DIR, "update-check.json");
1340
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
1341
+ var REGISTRY_URL = "https://registry.npmjs.org/github-labels-template/latest";
1342
+ function isNewerVersion(latest, current) {
1343
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
1344
+ const [lMaj, lMin, lPatch] = parse(latest);
1345
+ const [cMaj, cMin, cPatch] = parse(current);
1346
+ if (lMaj !== cMaj)
1347
+ return lMaj > cMaj;
1348
+ if (lMin !== cMin)
1349
+ return lMin > cMin;
1350
+ return lPatch > cPatch;
1351
+ }
1352
+ function readCache(cacheFile = CACHE_FILE) {
1353
+ try {
1354
+ if (!existsSync2(cacheFile))
1355
+ return null;
1356
+ const raw = readFileSync2(cacheFile, "utf-8");
1357
+ return JSON.parse(raw);
1358
+ } catch {
1359
+ return null;
1360
+ }
1361
+ }
1362
+ function writeCache(data, cacheFile = CACHE_FILE) {
1363
+ try {
1364
+ const dir = join(cacheFile, "..");
1365
+ if (!existsSync2(dir))
1366
+ mkdirSync(dir, { recursive: true });
1367
+ writeFileSync2(cacheFile, JSON.stringify(data), "utf-8");
1368
+ } catch {}
1369
+ }
1370
+ async function fetchLatestVersion() {
1371
+ try {
1372
+ const res = await fetch(REGISTRY_URL);
1373
+ if (!res.ok)
1374
+ return null;
1375
+ const data = await res.json();
1376
+ return data.version ?? null;
1377
+ } catch {
1378
+ return null;
1379
+ }
1380
+ }
1381
+ function refreshCacheInBackground(cacheFile = CACHE_FILE) {
1382
+ fetchLatestVersion().then((version) => {
1383
+ if (version) {
1384
+ writeCache({ lastChecked: Date.now(), latestVersion: version }, cacheFile);
1385
+ }
1386
+ }).catch(() => {});
1387
+ }
1388
+ function checkForUpdate(cacheFile = CACHE_FILE) {
1389
+ if (process.env.CI === "true" || process.env.CI === "1")
1390
+ return null;
1391
+ if (process.env.NO_UPDATE_NOTIFIER)
1392
+ return null;
1393
+ if (process.argv.includes("--no-update-notifier"))
1394
+ return null;
1395
+ const cache = readCache(cacheFile);
1396
+ const now = Date.now();
1397
+ const isStale = !cache || now - cache.lastChecked > CACHE_TTL_MS;
1398
+ if (isStale) {
1399
+ refreshCacheInBackground(cacheFile);
1400
+ }
1401
+ if (cache?.latestVersion && isNewerVersion(cache.latestVersion, getVersion())) {
1402
+ return cache.latestVersion;
1403
+ }
1404
+ return null;
1405
+ }
1406
+
1407
+ // src/commands/update.ts
1408
+ function detectPackageManager() {
1409
+ try {
1410
+ execSync("bun --version", { stdio: "ignore" });
1411
+ return "bun";
1412
+ } catch {
1413
+ return "npm";
1414
+ }
1415
+ }
1416
+ function getUpdateCommand(pm) {
1417
+ if (pm === "bun")
1418
+ return "bun add -g github-labels-template";
1419
+ return "npm install -g github-labels-template";
1420
+ }
1421
+ var update_default = defineCommand7({
1422
+ meta: {
1423
+ name: "update",
1424
+ description: "Update ghlt to the latest published version"
1425
+ },
1426
+ args: {
1427
+ check: {
1428
+ type: "boolean",
1429
+ default: false,
1430
+ description: "Only check if an update is available without installing"
1431
+ },
1432
+ "dry-run": {
1433
+ type: "boolean",
1434
+ default: false,
1435
+ description: "Alias for --check — report availability without updating"
1436
+ }
1437
+ },
1438
+ async run({ args }) {
1439
+ const current = getVersion();
1440
+ info(`Current version: v${current}`);
1441
+ const latest = await fetchLatestVersion();
1442
+ if (!latest) {
1443
+ error("Could not fetch the latest version. Check your internet connection.");
1444
+ process.exit(1);
1445
+ }
1446
+ info(`Latest version: v${latest}`);
1447
+ writeCache({ lastChecked: Date.now(), latestVersion: latest });
1448
+ if (!isNewerVersion(latest, current)) {
1449
+ success("Already on the latest version.");
1450
+ return;
1451
+ }
1452
+ if (args.check || args["dry-run"]) {
1453
+ info(`Update available: v${current} → v${latest}`);
1454
+ info(`Run 'ghlt update' to upgrade.`);
1455
+ return;
1456
+ }
1457
+ info("Updating ghlt...");
1458
+ try {
1459
+ const pm = detectPackageManager();
1460
+ const cmd = getUpdateCommand(pm);
1461
+ execSync(cmd, { stdio: "inherit" });
1462
+ success(`ghlt updated to v${latest}`);
1463
+ } catch {
1464
+ error("Update failed. Try running the update command manually:");
1465
+ console.log(` npm install -g github-labels-template`);
1466
+ process.exit(1);
1467
+ }
1468
+ }
1469
+ });
1470
+
1150
1471
  // src/index.ts
1151
1472
  var isHelp = process.argv.includes("--help") || process.argv.includes("-h");
1152
1473
  showBanner(isHelp);
1153
- var main = defineCommand6({
1474
+ var availableUpdate = checkForUpdate();
1475
+ if (availableUpdate) {
1476
+ showUpdateBanner(availableUpdate);
1477
+ }
1478
+ var main = defineCommand8({
1154
1479
  meta: {
1155
1480
  name: "ghlt",
1156
1481
  version: getVersion(),
@@ -1168,7 +1493,9 @@ var main = defineCommand6({
1168
1493
  wipe: wipe_default,
1169
1494
  migrate: migrate_default,
1170
1495
  generate: generate_default,
1171
- list: list_default
1496
+ list: list_default,
1497
+ check: check_default,
1498
+ update: update_default
1172
1499
  },
1173
1500
  run({ args }) {
1174
1501
  if (args.version) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-labels-template",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-patch.30e7740",
4
4
  "description": "A CLI tool to apply a curated GitHub labels template to any repository using gh CLI.",
5
5
  "type": "module",
6
6
  "bin": {