nodebb-plugin-unread-category-priority 1.0.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "filePath": "/Users/shlomo/.claude/plugins/cache/a5c-ai/babysitter/4.0.157/skills/babysit/SKILL.md",
3
+ "compressedContent": "description: Orchestrate via @babysitter. Use this skill when asked to babysit a run, orchestrate a process or whenever it is called explicitly. (babysit, babysitter, orchestrate, orchestrate a run, workflow, etc. allowed-tools: Read, Grep, Write, Task, Bash, Edit, Grep, Glob, WebFetch, WebSearch, Search, AskUserQuestion, TodoWrite, TodoRead, Skill, BashOutput, KillShell, MultiEdit, LS a5c/runs/<runId>/` through iterative execution. SDK_VERSION=$(node -e \"try{console. readFileSync('${CLAUDE_PLUGIN_ROOT}/versions. sdkVersion||'latest')}catch{console. sudo npm i -g @a5c-ai/babysitter-sdk@$SDK_VERSION **Alternatively, use the CLI alias:** `CLI=\"npx -y @a5c-ai/babysitter-sdk@$SDK_VERSION\"` make sure you have jq installed and available in the path. babysitter instructions:babysit-skill --harness claude-code --interactive For non-interactive mode (running with `-p` flag or no AskUserQuestion tool): babysitter instructions:babysit-skill --harness claude-code --no-interactive Follow the instructions returned by the command above to orchestrate the run.",
4
+ "originalTokens": 405,
5
+ "compressedTokens": 263,
6
+ "compressedAt": "2026-06-04T14:24:19.264Z"
7
+ }
@@ -0,0 +1,28 @@
1
+ [INFO] 2026-06-04T14:28:40.603Z handleHookRunStop started
2
+ [INFO] 2026-06-04T14:28:40.606Z Hook input received
3
+ [INFO] 2026-06-04T14:28:40.606Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Session ID: 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c
4
+ [INFO] 2026-06-04T14:28:40.606Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved pluginRoot: /Users/shlomo/.claude/plugins/cache/a5c-ai/babysitter/4.0.157
5
+ [INFO] 2026-06-04T14:28:40.606Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved stateDir: /Users/shlomo/.a5c/state
6
+ [INFO] 2026-06-04T14:28:40.606Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Checking session file at: /Users/shlomo/.a5c/state/3a7a1b56-d654-45b6-8bd4-bd9e1504d32c.md
7
+ [INFO] 2026-06-04T14:28:40.608Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] No run associated with session — allowing exit
8
+ [INFO] 2026-06-04T14:30:22.442Z handleHookRunStop started
9
+ [INFO] 2026-06-04T14:30:22.445Z Hook input received
10
+ [INFO] 2026-06-04T14:30:22.446Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Session ID: 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c
11
+ [INFO] 2026-06-04T14:30:22.446Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved pluginRoot: /Users/shlomo/.claude/plugins/cache/a5c-ai/babysitter/4.0.157
12
+ [INFO] 2026-06-04T14:30:22.446Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved stateDir: /Users/shlomo/.a5c/state
13
+ [INFO] 2026-06-04T14:30:22.446Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Checking session file at: /Users/shlomo/.a5c/state/3a7a1b56-d654-45b6-8bd4-bd9e1504d32c.md
14
+ [INFO] 2026-06-04T14:30:22.446Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] No active loop found for session 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c — allowing exit
15
+ [INFO] 2026-06-04T14:31:25.162Z handleHookRunStop started
16
+ [INFO] 2026-06-04T14:31:25.165Z Hook input received
17
+ [INFO] 2026-06-04T14:31:25.165Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Session ID: 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c
18
+ [INFO] 2026-06-04T14:31:25.166Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved pluginRoot: /Users/shlomo/.claude/plugins/cache/a5c-ai/babysitter/4.0.157
19
+ [INFO] 2026-06-04T14:31:25.166Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved stateDir: /Users/shlomo/.a5c/state
20
+ [INFO] 2026-06-04T14:31:25.166Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Checking session file at: /Users/shlomo/.a5c/state/3a7a1b56-d654-45b6-8bd4-bd9e1504d32c.md
21
+ [INFO] 2026-06-04T14:31:25.166Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] No active loop found for session 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c — allowing exit
22
+ [INFO] 2026-06-04T14:31:37.374Z handleHookRunStop started
23
+ [INFO] 2026-06-04T14:31:37.377Z Hook input received
24
+ [INFO] 2026-06-04T14:31:37.377Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Session ID: 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c
25
+ [INFO] 2026-06-04T14:31:37.377Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved pluginRoot: /Users/shlomo/.claude/plugins/cache/a5c-ai/babysitter/4.0.157
26
+ [INFO] 2026-06-04T14:31:37.377Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Resolved stateDir: /Users/shlomo/.a5c/state
27
+ [INFO] 2026-06-04T14:31:37.377Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] Checking session file at: /Users/shlomo/.a5c/state/3a7a1b56-d654-45b6-8bd4-bd9e1504d32c.md
28
+ [INFO] 2026-06-04T14:31:37.377Z [session=3a7a1b56-d654-45b6-8bd4-bd9e1504d32c] No active loop found for session 3a7a1b56-d654-45b6-8bd4-bd9e1504d32c — allowing exit
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # nodebb-plugin-unread-category-priority
2
+
3
+ Pins unread topics from one or more chosen categories to the **top** of the
4
+ Unread list, while preserving the normal recency order within each group.
5
+
6
+ No category is prioritized until you configure one in the ACP — the plugin is
7
+ a no-op until then.
8
+
9
+ ## How it works
10
+
11
+ NodeBB exposes the `filter:topics.getUnreadTids` hook, which fires after the
12
+ unread `tids` have been gathered and sorted by recency. This plugin hooks in,
13
+ looks up the `cid` of each unread topic via `topics.getTopicsFields`, and
14
+ reorders the list so topics in the priority categories appear first. The same
15
+ reordering is applied to every filter bucket (`''`, `new`, `watched`,
16
+ `unreplied`) so the order stays consistent across the Unread tabs.
17
+
18
+ No topics are added or removed — only reordered.
19
+
20
+ ## Configuration
21
+
22
+ Go to **ACP → Plugins → Unread Category Priority** and enter a comma-separated
23
+ list of category IDs (e.g. `5` or `5,3,8`). Leave it empty to disable. Settings
24
+ are cached in memory and refreshed automatically when you save.
25
+
26
+ ## Install
27
+
28
+ ```
29
+ npm install nodebb-plugin-unread-category-priority
30
+ ```
31
+
32
+ Then activate it in **ACP → Extend → Plugins** and rebuild/restart.
33
+
34
+ ## License
35
+
36
+ MIT
package/library.js ADDED
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const topics = require.main.require('./src/topics');
4
+ const meta = require.main.require('./src/meta');
5
+ const routeHelpers = require.main.require('./src/routes/helpers');
6
+
7
+ const plugin = {};
8
+
9
+ const SETTINGS_HASH = 'unread-category-priority';
10
+
11
+ // Empty until an admin configures at least one category; the plugin is a no-op while empty.
12
+ let priorityCids = [];
13
+
14
+ async function loadSettings() {
15
+ const settings = await meta.settings.get(SETTINGS_HASH);
16
+ const raw = (settings && settings.priorityCids) || '';
17
+ priorityCids = String(raw)
18
+ .split(',')
19
+ .map(cid => parseInt(cid.trim(), 10))
20
+ .filter(cid => !isNaN(cid) && cid > 0);
21
+ }
22
+
23
+ plugin.init = async function (params) {
24
+ const { router } = params;
25
+
26
+ routeHelpers.setupAdminPageRoute(router, '/admin/plugins/unread-category-priority', [], (req, res) => {
27
+ res.render('admin/plugins/unread-category-priority', {
28
+ title: 'Unread Category Priority',
29
+ });
30
+ });
31
+
32
+ await loadSettings();
33
+ };
34
+
35
+ plugin.addAdminNavigation = async function (header) {
36
+ header.plugins.push({
37
+ route: '/plugins/unread-category-priority',
38
+ icon: 'fa-arrow-up-wide-short',
39
+ name: 'Unread Category Priority',
40
+ });
41
+ return header;
42
+ };
43
+
44
+ // Move tids in a priority category to the front, keeping recency order within each group.
45
+ function reorder(tids, cidByTid) {
46
+ if (!Array.isArray(tids) || !tids.length) {
47
+ return tids;
48
+ }
49
+
50
+ const prioritySet = new Set(priorityCids);
51
+ const priority = [];
52
+ const rest = [];
53
+
54
+ tids.forEach((tid) => {
55
+ if (prioritySet.has(cidByTid[tid])) {
56
+ priority.push(tid);
57
+ } else {
58
+ rest.push(tid);
59
+ }
60
+ });
61
+
62
+ return priority.concat(rest);
63
+ }
64
+
65
+ plugin.prioritize = async function (data) {
66
+ if (!priorityCids.length) {
67
+ return data;
68
+ }
69
+
70
+ // Collect every tid across data.tids and all tidsByFilter buckets, so one cid
71
+ // lookup feeds a consistent reorder of all of them.
72
+ const allTids = new Set(data.tids || []);
73
+ Object.values(data.tidsByFilter || {}).forEach((list) => {
74
+ (list || []).forEach(tid => allTids.add(tid));
75
+ });
76
+
77
+ if (!allTids.size) {
78
+ return data;
79
+ }
80
+
81
+ const tidList = Array.from(allTids);
82
+ const fields = await topics.getTopicsFields(tidList, ['cid']);
83
+
84
+ const cidByTid = {};
85
+ tidList.forEach((tid, i) => {
86
+ cidByTid[tid] = parseInt(fields[i] && fields[i].cid, 10);
87
+ });
88
+
89
+ data.tids = reorder(data.tids, cidByTid);
90
+
91
+ if (data.tidsByFilter) {
92
+ Object.keys(data.tidsByFilter).forEach((filter) => {
93
+ data.tidsByFilter[filter] = reorder(data.tidsByFilter[filter], cidByTid);
94
+ });
95
+ }
96
+
97
+ return data;
98
+ };
99
+
100
+ plugin.onSettingsSave = async function (data) {
101
+ if (data && data.plugin === SETTINGS_HASH) {
102
+ await loadSettings();
103
+ }
104
+ };
105
+
106
+ module.exports = plugin;
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "nodebb-plugin-unread-category-priority",
3
+ "version": "1.0.0",
4
+ "description": "Boosts topics from a chosen category to the top of the Unread list",
5
+ "main": "library.js",
6
+ "keywords": [
7
+ "nodebb",
8
+ "plugin",
9
+ "unread",
10
+ "category",
11
+ "priority"
12
+ ],
13
+ "license": "MIT",
14
+ "nbbpm": {
15
+ "compatibility": "^3.0.0 || ^4.0.0"
16
+ }
17
+ }
package/plugin.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "nodebb-plugin-unread-category-priority",
3
+ "url": "https://github.com/nodebb/nodebb-plugin-unread-category-priority",
4
+ "library": "./library.js",
5
+ "hooks": [
6
+ { "hook": "static:app.load", "method": "init" },
7
+ { "hook": "filter:topics.getUnreadTids", "method": "prioritize" },
8
+ { "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
9
+ { "hook": "action:settings.set", "method": "onSettingsSave" }
10
+ ],
11
+ "modules": {
12
+ "../admin/plugins/unread-category-priority.js": "./static/lib/admin.js"
13
+ },
14
+ "templates": "static/templates",
15
+ "less": []
16
+ }
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ define('admin/plugins/unread-category-priority', ['settings', 'alerts'], function (settings, alerts) {
4
+ const ACP = {};
5
+
6
+ ACP.init = function () {
7
+ settings.load('unread-category-priority', $('.unread-category-priority-settings'));
8
+
9
+ $('#save').on('click', function () {
10
+ settings.save('unread-category-priority', $('.unread-category-priority-settings'), function () {
11
+ alerts.alert({
12
+ type: 'success',
13
+ alert_id: 'unread-category-priority-saved',
14
+ title: 'Settings saved',
15
+ message: 'Reload the page (or any unread view) to see the new priority order.',
16
+ timeout: 5000,
17
+ });
18
+ });
19
+ });
20
+ };
21
+
22
+ return ACP;
23
+ });
@@ -0,0 +1,22 @@
1
+ <div class="acp-page-container">
2
+ <div class="row m-0">
3
+ <div class="col-12 col-md-8 px-0">
4
+ <div class="d-flex border-bottom py-2 m-0 sticky-top acp-page-main-header align-items-center justify-content-between">
5
+ <h4 class="fw-bold tracking-tight mb-0">Unread Category Priority</h4>
6
+ <button id="save" class="btn btn-primary btn-sm">[[admin/admin:save-changes]]</button>
7
+ </div>
8
+
9
+ <form role="form" class="unread-category-priority-settings">
10
+ <div class="mb-3">
11
+ <label class="form-label" for="priorityCids">Priority category IDs</label>
12
+ <input type="text" id="priorityCids" name="priorityCids" class="form-control" placeholder="e.g. 5 or 5,3,8" />
13
+ <p class="form-text">
14
+ Comma-separated category IDs. Unread topics in these categories are pinned to the
15
+ top of the Unread list. Recency order is preserved within each group.
16
+ Leave empty to disable — the plugin does nothing until at least one category is set.
17
+ </p>
18
+ </div>
19
+ </form>
20
+ </div>
21
+ </div>
22
+ </div>