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.
- package/dist/app/assets/{backups-DrK5WGqD.js → backups-DUiBIjYh.js} +1 -1
- package/dist/app/assets/{badge-Drt9JvOu.js → badge-CVyqIkfu.js} +1 -1
- package/dist/app/assets/button-srM_gVNp.js +14 -0
- package/dist/app/assets/{coerce-Cxjr6MIU.js → coerce-hGm-kFoB.js} +1 -1
- package/dist/app/assets/explore-DfJsTl1a.js +33 -0
- package/dist/app/assets/{history-CN5yGwhZ.js → history-BeQkAZPX.js} +1 -1
- package/dist/app/assets/index-CEpF9MZu.css +2 -0
- package/dist/app/assets/index-Z9aIL5Tz.js +10 -0
- package/dist/app/assets/list._listId-BthjMh8l.js +2 -0
- package/dist/app/assets/{list._listId-DBB3l--n.js → list._listId-QMOV--8Q.js} +1 -1
- package/dist/app/assets/{local-backups-DLhiWDk4.js → local-backups-BlLTb6g5.js} +1 -1
- package/dist/app/assets/{local-sync-DiQe8mhd.js → local-sync-Cp5d59-N.js} +1 -1
- package/dist/app/assets/{registry-BKf8LDk2.js → registry-CDScSFs0.js} +2 -2
- package/dist/app/assets/{routes-BYr-dng4.js → routes-8llA2c3j.js} +2 -2
- package/dist/app/assets/{routes-DaKmAWCG.js → routes-CIYRvBg6.js} +2 -2
- package/dist/app/assets/{schemas-DVvZEnxp.js → schemas-CLMWt96T.js} +2 -2
- package/dist/app/assets/{star-BPue5Y4t.js → star-D-XjWe95.js} +1 -1
- package/dist/app/assets/{triangle-alert-BL8M65pd.js → triangle-alert-ILMBIuGB.js} +1 -1
- package/dist/app/index.html +9 -9
- package/dist/cli.js +167 -6
- package/package.json +1 -1
- package/dist/app/assets/button-r9cVFa0_.js +0 -14
- package/dist/app/assets/explore-Da5fm3tF.js +0 -33
- package/dist/app/assets/index-BuYchyhs.js +0 -10
- package/dist/app/assets/index-dVYqSW2P.css +0 -2
- package/dist/app/assets/list._listId-CNuYrPaQ.js +0 -2
package/dist/app/index.html
CHANGED
|
@@ -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-
|
|
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-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/schemas-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/star-
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/coerce-
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/history-
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/routes-
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/list._listId-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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({
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
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
|
+
"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",
|