github-stars-organizer 1.3.2 → 1.4.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 (26) hide show
  1. package/dist/app/assets/{backups-DrK5WGqD.js → backups-DUiBIjYh.js} +1 -1
  2. package/dist/app/assets/{badge-Drt9JvOu.js → badge-CVyqIkfu.js} +1 -1
  3. package/dist/app/assets/button-srM_gVNp.js +14 -0
  4. package/dist/app/assets/{coerce-Cxjr6MIU.js → coerce-hGm-kFoB.js} +1 -1
  5. package/dist/app/assets/explore-DfJsTl1a.js +33 -0
  6. package/dist/app/assets/{history-CN5yGwhZ.js → history-BeQkAZPX.js} +1 -1
  7. package/dist/app/assets/index-CEpF9MZu.css +2 -0
  8. package/dist/app/assets/index-Z9aIL5Tz.js +10 -0
  9. package/dist/app/assets/list._listId-BthjMh8l.js +2 -0
  10. package/dist/app/assets/{list._listId-DBB3l--n.js → list._listId-QMOV--8Q.js} +1 -1
  11. package/dist/app/assets/{local-backups-DLhiWDk4.js → local-backups-BlLTb6g5.js} +1 -1
  12. package/dist/app/assets/{local-sync-DiQe8mhd.js → local-sync-Cp5d59-N.js} +1 -1
  13. package/dist/app/assets/{registry-BKf8LDk2.js → registry-CDScSFs0.js} +2 -2
  14. package/dist/app/assets/{routes-BYr-dng4.js → routes-8llA2c3j.js} +2 -2
  15. package/dist/app/assets/{routes-DaKmAWCG.js → routes-CIYRvBg6.js} +2 -2
  16. package/dist/app/assets/{schemas-DVvZEnxp.js → schemas-CLMWt96T.js} +2 -2
  17. package/dist/app/assets/{star-BPue5Y4t.js → star-D-XjWe95.js} +1 -1
  18. package/dist/app/assets/{triangle-alert-BL8M65pd.js → triangle-alert-ILMBIuGB.js} +1 -1
  19. package/dist/app/index.html +9 -9
  20. package/dist/cli.js +167 -6
  21. package/package.json +1 -1
  22. package/dist/app/assets/button-r9cVFa0_.js +0 -14
  23. package/dist/app/assets/explore-Da5fm3tF.js +0 -33
  24. package/dist/app/assets/index-BuYchyhs.js +0 -10
  25. package/dist/app/assets/index-dVYqSW2P.css +0 -2
  26. package/dist/app/assets/list._listId-CNuYrPaQ.js +0 -2
@@ -17,16 +17,16 @@
17
17
  <meta property="og:image:height" content="600" />
18
18
  <meta property="og:image:alt" content="Stars Organizer: finally do something with your GitHub stars. Lists synced to GitHub, a health score per repo, tags, ratings and notes." />
19
19
  <meta name="twitter:card" content="summary_large_image" />
20
- <script type="module" crossorigin src="/assets/index-BuYchyhs.js"></script>
20
+ <script type="module" crossorigin src="/assets/index-Z9aIL5Tz.js"></script>
21
21
  <link rel="modulepreload" crossorigin href="/assets/sync-persistence-BJbWXISF.js">
22
- <link rel="modulepreload" crossorigin href="/assets/button-r9cVFa0_.js">
23
- <link rel="modulepreload" crossorigin href="/assets/schemas-DVvZEnxp.js">
24
- <link rel="modulepreload" crossorigin href="/assets/star-BPue5Y4t.js">
25
- <link rel="modulepreload" crossorigin href="/assets/coerce-Cxjr6MIU.js">
26
- <link rel="modulepreload" crossorigin href="/assets/history-CN5yGwhZ.js">
27
- <link rel="modulepreload" crossorigin href="/assets/routes-DaKmAWCG.js">
28
- <link rel="modulepreload" crossorigin href="/assets/list._listId-CNuYrPaQ.js">
29
- <link rel="stylesheet" crossorigin href="/assets/index-dVYqSW2P.css">
22
+ <link rel="modulepreload" crossorigin href="/assets/button-srM_gVNp.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/schemas-CLMWt96T.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/star-D-XjWe95.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/coerce-hGm-kFoB.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/history-BeQkAZPX.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/routes-CIYRvBg6.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/list._listId-BthjMh8l.js">
29
+ <link rel="stylesheet" crossorigin href="/assets/index-CEpF9MZu.css">
30
30
  </head>
31
31
  <body>
32
32
  <div id="root"></div>
package/dist/cli.js CHANGED
@@ -1270,6 +1270,13 @@ function addReposToLists(client, additions) {
1270
1270
  async function addStar(client, repoNodeId) {
1271
1271
  await client.graphql(`mutation($input: AddStarInput!) { addStar(input: $input) { starrable { id } } }`, { input: { starrableId: repoNodeId } });
1272
1272
  }
1273
+ var SELF_REPO = {
1274
+ owner: "armanckeser",
1275
+ name: "github-stars-organizer"
1276
+ };
1277
+ async function starSelf(client) {
1278
+ await client.rest("PUT", `/user/starred/${SELF_REPO.owner}/${SELF_REPO.name}`);
1279
+ }
1273
1280
  async function unstar(client, owner, name) {
1274
1281
  await client.rest("DELETE", `/user/starred/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);
1275
1282
  }
@@ -7377,6 +7384,10 @@ function createLocalServer({ github, appDir, port, store, login }) {
7377
7384
  const listIds = await changeRepoList(github, await c.req.json());
7378
7385
  return c.json({ listIds });
7379
7386
  });
7387
+ api.post("/star-self", async (c) => {
7388
+ await starSelf(github);
7389
+ return c.json({});
7390
+ });
7380
7391
  api.post("/unstar", async (c) => {
7381
7392
  const { owner, name } = await c.req.json();
7382
7393
  await unstar(github, owner, name);
@@ -7463,14 +7474,116 @@ async function launch({ github, login, home, appDir, port }) {
7463
7474
  };
7464
7475
  }
7465
7476
  //#endregion
7477
+ //#region src/lib/star-waves.ts
7478
+ var QUIET_AFTER_MS = 182 * 864e5;
7479
+ function isQuiet(repo, now) {
7480
+ if (repo.is_archived) return true;
7481
+ if (!repo.pushed_at) return true;
7482
+ return now - Date.parse(repo.pushed_at) >= QUIET_AFTER_MS;
7483
+ }
7484
+ var DEFAULT_LIMIT = 10;
7485
+ var DESCRIPTION_CHARS = 100;
7486
+ function indexFile(home) {
7487
+ return path.join(home, "search-index.json");
7488
+ }
7489
+ async function readIndex(home) {
7490
+ try {
7491
+ return JSON.parse(await readFile(indexFile(home), "utf8"));
7492
+ } catch {
7493
+ return null;
7494
+ }
7495
+ }
7496
+ function isStale(index, now) {
7497
+ return !index || now - Date.parse(index.refreshedAt) >= 864e5;
7498
+ }
7499
+ /** Every star with the names of the Lists it is in, read fresh from GitHub. */
7500
+ async function buildIndex(github, login, now) {
7501
+ const lists = await fetchViewerLists(github.graphql);
7502
+ const listsOf = /* @__PURE__ */ new Map();
7503
+ for (const list of lists) for (const repo of list.repos) listsOf.set(repo.fullName, [...listsOf.get(repo.fullName) ?? [], list.name]);
7504
+ const stars = [];
7505
+ for await (const edges of starredPages(github)) for (const { node, starredAt } of edges) stars.push({
7506
+ fullName: node.nameWithOwner,
7507
+ description: node.description,
7508
+ language: node.primaryLanguage?.name ?? null,
7509
+ topics: topicsOf(node).split(",").filter(Boolean),
7510
+ stars: node.stargazerCount,
7511
+ isArchived: node.isArchived ?? false,
7512
+ pushedAt: node.pushedAt ?? null,
7513
+ starredAt,
7514
+ lists: listsOf.get(node.nameWithOwner) ?? []
7515
+ });
7516
+ return {
7517
+ login,
7518
+ refreshedAt: now.toISOString(),
7519
+ stars
7520
+ };
7521
+ }
7522
+ async function saveIndex(home, index) {
7523
+ await mkdir(home, { recursive: true });
7524
+ const file = indexFile(home);
7525
+ const partial = `${file}.${randomUUID()}.partial`;
7526
+ await writeFile(partial, JSON.stringify(index));
7527
+ await rename(partial, file);
7528
+ }
7529
+ function termsOf(query) {
7530
+ return [...new Set(query.toLowerCase().split(/[\s,]+/).filter((term) => term.length > 1))];
7531
+ }
7532
+ /**
7533
+ * Stars matching any of the words, best first. A word in the repo name counts
7534
+ * most, then topics and List names, then the description and language.
7535
+ */
7536
+ function searchStars(stars, query, limit = DEFAULT_LIMIT) {
7537
+ const terms = termsOf(query);
7538
+ if (!terms.length) return [];
7539
+ return stars.map((star) => {
7540
+ const name = star.fullName.toLowerCase();
7541
+ const tags = [...star.topics, ...star.lists].join(" ").toLowerCase();
7542
+ const text = `${star.description ?? ""} ${star.language ?? ""}`.toLowerCase();
7543
+ let score = 0;
7544
+ for (const term of terms) {
7545
+ if (name.includes(term)) score += 3;
7546
+ if (tags.includes(term)) score += 2;
7547
+ if (text.includes(term)) score += 1;
7548
+ }
7549
+ return {
7550
+ star,
7551
+ score
7552
+ };
7553
+ }).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score || b.star.stars - a.star.stars).slice(0, limit).map(({ star }) => star);
7554
+ }
7555
+ function ago(iso, now) {
7556
+ const days = Math.floor((now - Date.parse(iso)) / 864e5);
7557
+ if (days < 1) return "today";
7558
+ if (days < 60) return `${days}d ago`;
7559
+ if (days < 730) return `${Math.floor(days / 30)}mo ago`;
7560
+ return `${Math.floor(days / 365)}y ago`;
7561
+ }
7562
+ function compactCount(n) {
7563
+ return n >= 1e3 ? `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k` : String(n);
7564
+ }
7565
+ /** One line per star for an agent to read: name, size, Lists, and whether it is still alive. */
7566
+ function formatResults(results, now) {
7567
+ return results.map((star) => {
7568
+ const state = star.isArchived ? "ARCHIVED" : isQuiet({
7569
+ is_archived: false,
7570
+ pushed_at: star.pushedAt
7571
+ }, now) ? `QUIET, last push ${star.pushedAt ? ago(star.pushedAt, now) : "never"}` : `active, last push ${star.pushedAt ? ago(star.pushedAt, now) : "unknown"}`;
7572
+ const lists = star.lists.length ? ` · Lists: ${star.lists.join(", ")}` : "";
7573
+ const description = star.description ? `\n ${star.description.length > DESCRIPTION_CHARS ? `${star.description.slice(0, DESCRIPTION_CHARS - 1)}…` : star.description}` : "";
7574
+ return `${star.fullName} · ${compactCount(star.stars)} stars · ${state}${lists}${description}`;
7575
+ }).join("\n");
7576
+ }
7577
+ //#endregion
7466
7578
  //#region cli/src/cli.ts
7467
7579
  var DEFAULT_PORT = 5185;
7468
7580
  var EXIT_FAILURE = 1;
7469
- var NOTICE = `Stars Organizer v1.3.2 Copyright (C) 2026 Armanc Keser
7581
+ var NOTICE = `Stars Organizer v1.4.0 Copyright (C) 2026 Armanc Keser
7470
7582
  Free software under the GNU AGPL v3 or later, with ABSOLUTELY NO WARRANTY.
7471
7583
  Source, license and bundled licenses: https://github.com/armanckeser/github-stars-organizer
7472
7584
  Not affiliated with or endorsed by GitHub.`;
7473
7585
  var HELP = `Usage: npx github-stars-organizer [--port <number>] [--no-open]
7586
+ npx github-stars-organizer search <words> [--refresh] [--json] [--limit <n>]
7474
7587
 
7475
7588
  Opens Stars Organizer on http://127.0.0.1:${DEFAULT_PORT} using your GitHub
7476
7589
  login from the gh CLI (or GITHUB_TOKEN, if set). Tags, ratings and notes are
@@ -7480,6 +7593,12 @@ saved in this browser; Lists and unstars are written to GitHub.
7480
7593
  --no-open Print the address without opening a browser.
7481
7594
  --help Show this message.
7482
7595
 
7596
+ search prints the stars that match any of the words (name, topics, Lists,
7597
+ description), with each repo's Lists and whether it is archived or quiet
7598
+ (no push in 6 months). It reads a copy of your stars kept on disk and
7599
+ refreshes it from GitHub once a day, or now with --refresh. For coding
7600
+ agents: check the user's stars before suggesting a library or tool.
7601
+
7483
7602
  Before opening, it backs up your stars and Lists to
7484
7603
  ~/.github-stars-organizer/backups (set ${HOME_ENV} to move it).
7485
7604
  It talks only to api.github.com, with your token; nothing else is sent anywhere.
@@ -7530,16 +7649,58 @@ function parsePort(value) {
7530
7649
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new CliError(`--port must be a number from 1 to 65535, got "${value}".`);
7531
7650
  return port;
7532
7651
  }
7652
+ function parseLimit(value) {
7653
+ if (value === void 0) return void 0;
7654
+ const limit = Number(value);
7655
+ if (!Number.isInteger(limit) || limit < 1) throw new CliError(`--limit must be a whole number above 0, got "${value}".`);
7656
+ return limit;
7657
+ }
7658
+ async function search(words, options) {
7659
+ const query = words.join(" ").trim();
7660
+ if (!query) throw new CliError("Say what to look for, e.g. `npx github-stars-organizer search pdf parser`.");
7661
+ const limit = parseLimit(options.limit);
7662
+ const home = defaultHome();
7663
+ const now = /* @__PURE__ */ new Date();
7664
+ let index = await readIndex(home);
7665
+ if (options.refresh || isStale(index, now.getTime())) {
7666
+ const github = createGitHubClient(await resolveToken());
7667
+ console.error("Reading your stars and Lists from GitHub (once a day; about 30 seconds for 500 stars)...");
7668
+ index = await buildIndex(github, await signedInAs(github), now);
7669
+ await saveIndex(home, index);
7670
+ }
7671
+ const results = searchStars(index.stars, query, limit);
7672
+ if (options.json) {
7673
+ console.log(JSON.stringify({
7674
+ login: index.login,
7675
+ refreshedAt: index.refreshedAt,
7676
+ results
7677
+ }, null, 2));
7678
+ return;
7679
+ }
7680
+ if (!results.length) {
7681
+ console.log(`None of @${index.login}'s ${index.stars.length} stars match "${query}".`);
7682
+ return;
7683
+ }
7684
+ console.log(formatResults(results, now.getTime()));
7685
+ }
7533
7686
  async function main() {
7534
- const { values } = parseArgs({ options: {
7535
- port: { type: "string" },
7536
- "no-open": { type: "boolean" },
7537
- help: { type: "boolean" }
7538
- } });
7687
+ const { values, positionals } = parseArgs({
7688
+ allowPositionals: true,
7689
+ options: {
7690
+ port: { type: "string" },
7691
+ "no-open": { type: "boolean" },
7692
+ help: { type: "boolean" },
7693
+ refresh: { type: "boolean" },
7694
+ json: { type: "boolean" },
7695
+ limit: { type: "string" }
7696
+ }
7697
+ });
7539
7698
  if (values.help) {
7540
7699
  console.log(HELP);
7541
7700
  return;
7542
7701
  }
7702
+ if (positionals[0] === "search") return search(positionals.slice(1), values);
7703
+ if (positionals.length) throw new CliError(`Unknown command "${positionals[0]}". Run with --help to see the options.`);
7543
7704
  console.log(`${NOTICE}\n`);
7544
7705
  const port = parsePort(values.port);
7545
7706
  const url = `http://127.0.0.1:${port}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-stars-organizer",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Search, tag, rate and sort your GitHub stars into Lists, using your gh login. Runs locally; nothing to host.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "Armanc Keser",