oh-my-hi 0.1.2 β 0.2.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/CHANGELOG.md +10 -0
- package/GUIDE.md +163 -0
- package/README.md +24 -0
- package/assets/dashboard.png +0 -0
- package/assets/insights.png +0 -0
- package/assets/token-overview.png +0 -0
- package/package.json +6 -2
- package/scripts/generate-dashboard.mjs +93 -75
- package/templates/app.js +210 -535
- package/templates/dashboard.html +5 -5
- package/templates/locales/en.json +326 -0
- package/templates/locales/ko.json +121 -5
- package/templates/styles.css +1 -0
- package/templates/work-types.json +79 -0
- package/.claude/settings.local.json +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.3] - 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Dashboard guide document (GUIDE.md) with detailed walkthrough of each section
|
|
7
|
+
- Update instructions in README and Help page (CLI + in-session commands)
|
|
8
|
+
- Shell-styled command block in Help page
|
|
9
|
+
- Privacy section in README
|
|
10
|
+
- Improved `--data-only` and `--enable-auto` descriptions with bookmark/refresh tips
|
|
11
|
+
- Version display in sidebar now auto-injected from package.json at build time
|
|
12
|
+
|
|
3
13
|
## [0.1.2] - 2026-03-31
|
|
4
14
|
|
|
5
15
|
### Added
|
package/GUIDE.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Dashboard Guide
|
|
2
|
+
|
|
3
|
+
A guide to each section of the oh-my-hi dashboard.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Sidebar
|
|
7
|
+
|
|
8
|
+
The left sidebar provides navigation, workspace selection, and date range filtering.
|
|
9
|
+
|
|
10
|
+
### Workspace Selector
|
|
11
|
+
|
|
12
|
+
Switch between **Global** (your entire Claude Code configuration) and **per-project** scopes. Each scope shows only the harness components and usage data relevant to that workspace.
|
|
13
|
+
|
|
14
|
+
### Date Range
|
|
15
|
+
|
|
16
|
+
Filter all usage data by time period:
|
|
17
|
+
|
|
18
|
+
| Button | Description |
|
|
19
|
+
|--------|-------------|
|
|
20
|
+
| **7d** | Last 7 days (today inclusive) |
|
|
21
|
+
| **30d** | Last 30 days (today inclusive) |
|
|
22
|
+
| **All** | All collected data since first session |
|
|
23
|
+
| **π
** | Custom date range picker |
|
|
24
|
+
|
|
25
|
+
Hover over a non-active button to preview the date range in a tooltip. The active range is shown below the buttons.
|
|
26
|
+
|
|
27
|
+
### Search
|
|
28
|
+
|
|
29
|
+
Type to filter sidebar items by name. Matches are highlighted across all categories.
|
|
30
|
+
|
|
31
|
+
### Categories
|
|
32
|
+
|
|
33
|
+
Below the main navigation, each harness component category is listed with an item count badge. Click to expand and see individual items; click an item to view its detail page.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Main Pages
|
|
38
|
+
|
|
39
|
+
### π Harness (Overview)
|
|
40
|
+
|
|
41
|
+
The landing page showing a high-level summary of your harness activity.
|
|
42
|
+
|
|
43
|
+
| Section | Description |
|
|
44
|
+
|---------|-------------|
|
|
45
|
+
| **Stat Cards** | Total usage, skill calls, agent calls, and command invocations for the selected period. Each card shows the change percentage vs. the previous period. |
|
|
46
|
+
| **Category Distribution** | Donut chart showing the proportion of skills, agents, and commands. |
|
|
47
|
+
| **Daily Trend** | Line chart of daily activity (skills + agents + commands combined). |
|
|
48
|
+
| **Popular Skills** | Top 5 most-used skills ranked by call count. |
|
|
49
|
+
| **Activity Heatmap** | GitHub-style calendar heatmap of daily activity intensity. |
|
|
50
|
+
| **Recent Activity** | Timeline of the 10 most recent skill, agent, and command invocations. |
|
|
51
|
+
| **Unused Items** | Skills and agents that were registered but never called in the selected period. |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### πͺ Tokens (Overview)
|
|
56
|
+
|
|
57
|
+
Token usage analytics across all Claude Code sessions.
|
|
58
|
+
|
|
59
|
+
| Section | Description |
|
|
60
|
+
|---------|-------------|
|
|
61
|
+
| **Stat Cards** | Total tokens, input tokens, output tokens, cache tokens β each with period-over-period change. |
|
|
62
|
+
| **Estimated Cost** | Total cost, daily average cost, and per-model cost cards. Based on Anthropic API token pricing (not actual CLI subscription billing). |
|
|
63
|
+
| **Cost Calculation** | Expandable section with the pricing formula, collapsible model pricing table (per 1M tokens), and a link to [anthropic.com/pricing](https://www.anthropic.com/pricing). |
|
|
64
|
+
| **Model Distribution** | Donut chart of token usage by model. |
|
|
65
|
+
| **Daily Token Usage** | Line chart of daily token consumption trend. |
|
|
66
|
+
| **Token Activity** | Calendar heatmap of daily token usage intensity. |
|
|
67
|
+
| **Token Usage by Model** | Table breakdown: input, output, cache, total tokens, and estimated cost per model. |
|
|
68
|
+
| **Token Insights** | Auto-generated analysis cards covering cache efficiency, response efficiency, model usage, cost breakdown, daily patterns, and peak hours. |
|
|
69
|
+
|
|
70
|
+
#### Cost Calculation Formula
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Cost = (Input Γ input price + Output Γ output price
|
|
74
|
+
+ Cache Read Γ cache read price + Cache Write Γ cache write price) Γ· 1,000,000
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Prices are per 1M tokens (USD), sourced from Anthropic's official API pricing. Claude Code CLI is subscription-based, so this estimate is for reference only.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### π Tokens > Analysis
|
|
82
|
+
|
|
83
|
+
Deeper analysis of token consumption patterns.
|
|
84
|
+
|
|
85
|
+
| Section | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| **Tokens by Task Category** | Horizontal bar chart grouping token usage by auto-classified work type (code editing, documentation, planning, etc.). Categories are derived from skill/agent descriptions and saved in `task-categories.json` for customization. |
|
|
88
|
+
| **Token Usage by Tool** | Horizontal bar chart of tokens attributed to specific skills, agents, or tools (top 10). |
|
|
89
|
+
| **Prompt Statistics** | Total prompts, average prompt length (chars), short prompt ratio (β€100 chars), long prompt ratio (β₯500 chars). |
|
|
90
|
+
| **Response Latency** | Average, median (P50), 95th percentile (P95), and max response time (humanβassistant interval). |
|
|
91
|
+
| **Session Analysis** | Total sessions, average messages per session, average session duration, longest session. |
|
|
92
|
+
| **Hourly Token Distribution** | Bar chart of token usage by hour of day (24h). |
|
|
93
|
+
| **Cache Efficiency** | Fresh input, cache read, cache creation token counts with percentages and overall cache hit rate. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### ποΈ Structure
|
|
98
|
+
|
|
99
|
+
Visual overview of your harness architecture.
|
|
100
|
+
|
|
101
|
+
| Section | Description |
|
|
102
|
+
|---------|-------------|
|
|
103
|
+
| **Component Flow** | SVG diagram showing how harness components relate to each other, grouped into three layers: **Context** (auto-loaded: CLAUDE.md, rules, principles, memory), **Event-driven** (hooks, MCP servers, plugins), and **User-invoked** (skills, agents, commands, teams, plans). |
|
|
104
|
+
| **File Tree** | Expandable tree view listing every registered component by category. Click any item to jump to its detail page. Plugins show active/inactive badges. |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### Category Pages
|
|
109
|
+
|
|
110
|
+
Click any category in the sidebar (Skills, Agents, Plugins, Hooks, etc.) to see a dedicated page for that category.
|
|
111
|
+
|
|
112
|
+
| Section | Description |
|
|
113
|
+
|---------|-------------|
|
|
114
|
+
| **Stat Cards** | Total items, usage count, and period-over-period change. |
|
|
115
|
+
| **Popular Items** | Top 5 most-used items within the category (ranked cards). |
|
|
116
|
+
| **Activity Heatmap** | Calendar heatmap filtered to this category only. |
|
|
117
|
+
| **Recent Activity** | Latest invocations of items in this category. |
|
|
118
|
+
| **Unused Items** | Registered items with zero calls in the selected period. |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### Item Detail Pages
|
|
123
|
+
|
|
124
|
+
Click any individual item (skill, agent, plugin, etc.) to see its detail page.
|
|
125
|
+
|
|
126
|
+
| Section | Description |
|
|
127
|
+
|---------|-------------|
|
|
128
|
+
| **Header** | Item name, type badge, file path, and description (from frontmatter). |
|
|
129
|
+
| **Metadata** | Parsed frontmatter fields displayed in a formatted card. |
|
|
130
|
+
| **Content Preview** | Full content of the item's definition file (markdown rendered). |
|
|
131
|
+
| **Usage Stats** | Call count and activity heatmap for this specific item (for skills, agents, commands). |
|
|
132
|
+
| **Related Items** | For plugins: list of skills provided by the plugin. For skills: parent plugin link if applicable. |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### β Help
|
|
137
|
+
|
|
138
|
+
In-dashboard reference page.
|
|
139
|
+
|
|
140
|
+
| Section | Description |
|
|
141
|
+
|---------|-------------|
|
|
142
|
+
| **Usage** | How to run the `/omh` command. |
|
|
143
|
+
| **Parameters** | Table of all command parameters (`--data-only`, `--enable-auto`, etc.). |
|
|
144
|
+
| **Data Sources** | Reference for each data parser: what files are read and what data is extracted (config files, skills, agents, plugins, hooks, memory, MCP servers, rules, commands, teams, plans, todos, scopes). |
|
|
145
|
+
| **Token & Activity** | How token usage, prompt stats, latency, and activity data are parsed from transcript JSONL files. |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## General Features
|
|
150
|
+
|
|
151
|
+
### Dark / Light Mode
|
|
152
|
+
|
|
153
|
+
Toggle via the theme button in the sidebar footer. Preference is persisted in `localStorage`.
|
|
154
|
+
|
|
155
|
+
### Period Comparison
|
|
156
|
+
|
|
157
|
+
Stat cards with a change percentage compare the selected period against the immediately preceding period of equal length. For example, 7d compares the last 7 days vs. the 7 days before that.
|
|
158
|
+
|
|
159
|
+
### Data Refresh
|
|
160
|
+
|
|
161
|
+
Run `/omh --data-only` to regenerate data and the web-ui without opening a new browser tab. Bookmark the generated local file (`output/index.html`) and refresh the page anytime to see the latest data.
|
|
162
|
+
|
|
163
|
+
Enable auto-refresh with `/omh --enable-auto` to rebuild data on every session end β just refresh the bookmarked tab to see updates.
|
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
Parses your entire Claude Code configuration and usage data, then generates an interactive single-file HTML dashboard you can open locally.
|
|
7
7
|
|
|
8
|
+
<img src="./assets/dashboard.png" alt="Dashboard preview" width="800">
|
|
8
9
|
|
|
9
10
|
## What it shows
|
|
10
11
|
|
|
@@ -14,6 +15,9 @@ Parses your entire Claude Code configuration and usage data, then generates an i
|
|
|
14
15
|
- **Task categories** β auto-classified token usage by work type (code editing, docs, planning, etc.)
|
|
15
16
|
- **Multi-workspace** β switch between global and per-project scopes
|
|
16
17
|
|
|
18
|
+
<img src="./assets/token-overview.png" alt="Tokens" width="800">
|
|
19
|
+
<img src="./assets/insights.png" alt="Insights" width="800">
|
|
20
|
+
|
|
17
21
|
## Installation
|
|
18
22
|
|
|
19
23
|
#### From the Command Line
|
|
@@ -33,6 +37,18 @@ claude plugin install oh-my-hi
|
|
|
33
37
|
/plugin install oh-my-hi@oh-my-hi-marketplace
|
|
34
38
|
```
|
|
35
39
|
|
|
40
|
+
## Update
|
|
41
|
+
|
|
42
|
+
To update to the latest version, run the same install command:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# From the Command Line
|
|
46
|
+
$ claude plugin install oh-my-hi
|
|
47
|
+
|
|
48
|
+
# Claude Code (in-session)
|
|
49
|
+
/plugin install oh-my-hi@oh-my-hi-marketplace
|
|
50
|
+
```
|
|
51
|
+
|
|
36
52
|
## Usage
|
|
37
53
|
|
|
38
54
|
Run in Claude Code:
|
|
@@ -64,6 +80,10 @@ Enable automatic data refresh so the dashboard stays up to date:
|
|
|
64
80
|
|
|
65
81
|
This registers a Stop hook that rebuilds the dashboard data whenever a Claude Code session ends. Refresh the browser tab to see the latest data.
|
|
66
82
|
|
|
83
|
+
## Guide
|
|
84
|
+
|
|
85
|
+
See **[GUIDE.md](GUIDE.md)** for a detailed walkthrough of each dashboard section β overview, token analytics, cost estimation, structure view, category pages, and more.
|
|
86
|
+
|
|
67
87
|
## How it works
|
|
68
88
|
|
|
69
89
|
1. **Parse** β Reads your Claude Code config directory for skills, agents, plugins, hooks, memory, MCP servers, rules, principles, commands, teams, plans, and usage transcripts
|
|
@@ -77,6 +97,10 @@ This registers a Stop hook that rebuilds the dashboard data whenever a Claude Co
|
|
|
77
97
|
- **English**: Built-in default
|
|
78
98
|
- **Other languages**: A template locale file is auto-generated on first build. Translate it and rebuild
|
|
79
99
|
|
|
100
|
+
## Privacy
|
|
101
|
+
|
|
102
|
+
All data stays on your machine. `oh-my-hi` reads only local Claude Code config files and transcripts β nothing is sent to external servers. The generated dashboard is a standalone HTML file opened via `file://` protocol, with no network requests. Your usage data, token statistics, and configuration details never leave your local environment.
|
|
103
|
+
|
|
80
104
|
## Browser support
|
|
81
105
|
|
|
82
106
|
The dashboard opens as a local `file://` HTML file. No web server required.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-hi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Claude Code harness insights dashboard",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,5 +16,9 @@
|
|
|
16
16
|
"insights",
|
|
17
17
|
"analytics"
|
|
18
18
|
],
|
|
19
|
-
"homepage": "https://github.com/netil/oh-my-hi"
|
|
19
|
+
"homepage": "https://github.com/netil/oh-my-hi",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"billboard.js": "^3.18.0",
|
|
22
|
+
"esbuild": "^0.27.4"
|
|
23
|
+
}
|
|
20
24
|
}
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
+
import { transformSync } from 'esbuild';
|
|
7
8
|
|
|
8
9
|
import { parseSkills } from './parsers/skills.mjs';
|
|
9
10
|
import { parseAgents } from './parsers/agents.mjs';
|
|
@@ -35,7 +36,7 @@ if (args.includes('--help')) {
|
|
|
35
36
|
|
|
36
37
|
Usage:
|
|
37
38
|
oh-my-hi Full build (index.html + data.json)
|
|
38
|
-
oh-my-hi --data-only
|
|
39
|
+
oh-my-hi --data-only Regenerate data + web-ui (skip browser open)
|
|
39
40
|
oh-my-hi --enable-auto Enable auto data refresh on session end
|
|
40
41
|
oh-my-hi --disable-auto Disable auto data refresh
|
|
41
42
|
oh-my-hi --status Check auto-refresh status
|
|
@@ -180,8 +181,60 @@ async function main() {
|
|
|
180
181
|
const safeJson = JSON.stringify(data);
|
|
181
182
|
fs.writeFileSync(dataPath, safeJson, 'utf-8');
|
|
182
183
|
|
|
183
|
-
//
|
|
184
|
+
// 5a-2) Minify usage data for inline HTML (key shortening + sessionId indexing)
|
|
185
|
+
const USAGE_KEY_MAP = {
|
|
186
|
+
timestamp: 'ts', model: 'm', inputTokens: 'it', outputTokens: 'ot',
|
|
187
|
+
cacheRead: 'cr', cacheCreation: 'cc', rawInput: 'ri', context: 'cx',
|
|
188
|
+
contextName: 'cn', sessionId: 'sid', latencyMs: 'ms', charLen: 'cl',
|
|
189
|
+
name: 'n', tool: 't', count: 'c', date: 'd', command: 'cmd', project: 'p',
|
|
190
|
+
messageCount: 'mc', sessionCount: 'sc', toolCallCount: 'tc',
|
|
191
|
+
};
|
|
192
|
+
function minifyUsageData(scopeDataObj) {
|
|
193
|
+
const result = {};
|
|
194
|
+
for (const [scope, sdata] of Object.entries(scopeDataObj)) {
|
|
195
|
+
const copy = { ...sdata };
|
|
196
|
+
if (copy.usage) {
|
|
197
|
+
const u = copy.usage;
|
|
198
|
+
// Build sessionId lookup table
|
|
199
|
+
const sidSet = new Set();
|
|
200
|
+
for (const arr of Object.values(u)) {
|
|
201
|
+
if (!Array.isArray(arr)) continue;
|
|
202
|
+
for (const item of arr) {
|
|
203
|
+
if (item.sessionId != null) sidSet.add(item.sessionId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const sidList = [...sidSet];
|
|
207
|
+
const sidIndex = Object.fromEntries(sidList.map((s, i) => [s, i]));
|
|
208
|
+
|
|
209
|
+
// Shorten keys and replace sessionId with index
|
|
210
|
+
const minUsage = {};
|
|
211
|
+
for (const [field, arr] of Object.entries(u)) {
|
|
212
|
+
if (!Array.isArray(arr)) { minUsage[field] = arr; continue; }
|
|
213
|
+
minUsage[field] = arr.map(item => {
|
|
214
|
+
const obj = {};
|
|
215
|
+
for (const [k, v] of Object.entries(item)) {
|
|
216
|
+
if (k === 'sessionId') {
|
|
217
|
+
obj.sid = v != null ? sidIndex[v] : null;
|
|
218
|
+
} else {
|
|
219
|
+
obj[USAGE_KEY_MAP[k] || k] = v;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return obj;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
minUsage._sidList = sidList;
|
|
226
|
+
copy.usage = minUsage;
|
|
227
|
+
}
|
|
228
|
+
result[scope] = copy;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
const inlineData = { ...data, scopeData: minifyUsageData(data.scopeData), _minified: true };
|
|
233
|
+
const inlineJson = JSON.stringify(inlineData);
|
|
234
|
+
|
|
235
|
+
// 5b) Load locales
|
|
184
236
|
const LOCALES_DIR = path.join(TEMPLATES, 'locales');
|
|
237
|
+
const enObj = JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, 'en.json'), 'utf-8'));
|
|
185
238
|
let localeObj = {};
|
|
186
239
|
const localePath = path.join(LOCALES_DIR, systemLocale + '.json');
|
|
187
240
|
if (systemLocale !== 'en' && fs.existsSync(localePath)) {
|
|
@@ -191,42 +244,53 @@ async function main() {
|
|
|
191
244
|
console.log(` locale: ${systemLocale} (${Object.keys(localeObj).length - 1} keys)`);
|
|
192
245
|
} catch { /* fallback to English */ }
|
|
193
246
|
}
|
|
194
|
-
// For unknown locales:
|
|
247
|
+
// For unknown locales: copy en.json as template on first run
|
|
195
248
|
if (systemLocale !== 'en' && !fs.existsSync(localePath)) {
|
|
196
|
-
const enKeys = {};
|
|
197
|
-
const appSrc = fs.readFileSync(path.join(TEMPLATES, 'app.js'), 'utf-8');
|
|
198
|
-
const enMatch = appSrc.match(/I18N\.en\s*=\s*\{([\s\S]*?)\n \};/);
|
|
199
|
-
if (enMatch) {
|
|
200
|
-
for (const line of enMatch[1].split('\n')) {
|
|
201
|
-
const m = line.match(/^\s*(\w+):\s*"(.*)"/);
|
|
202
|
-
if (m) enKeys[m[1]] = m[2];
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
249
|
fs.mkdirSync(LOCALES_DIR, { recursive: true });
|
|
206
|
-
fs.writeFileSync(localePath, JSON.stringify(
|
|
250
|
+
fs.writeFileSync(localePath, JSON.stringify(enObj, null, 2), 'utf-8');
|
|
207
251
|
console.log(` locale: created template ${localePath} (translate and rebuild)`);
|
|
208
252
|
}
|
|
209
253
|
|
|
210
254
|
// 5c) index.html β always regenerated (data is inlined for file:// compatibility)
|
|
211
255
|
const template = fs.readFileSync(path.join(TEMPLATES, 'dashboard.html'), 'utf-8');
|
|
212
|
-
const
|
|
213
|
-
const
|
|
256
|
+
const rawStyles = fs.readFileSync(path.join(TEMPLATES, 'styles.css'), 'utf-8');
|
|
257
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
258
|
+
const rawAppJs = fs.readFileSync(path.join(TEMPLATES, 'app.js'), 'utf-8')
|
|
259
|
+
.replace(/__VERSION__/g, JSON.stringify(pkg.version));
|
|
260
|
+
|
|
261
|
+
// billboard.js (pkgd includes d3) + CSS
|
|
262
|
+
const bbDir = path.join(ROOT, 'node_modules', 'billboard.js', 'dist');
|
|
263
|
+
if (!fs.existsSync(bbDir)) {
|
|
264
|
+
console.error('oh-my-hi: β node_modules not found. Run `npm install` in', ROOT);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
const bbJs = fs.readFileSync(path.join(bbDir, 'billboard.pkgd.min.js'), 'utf-8');
|
|
268
|
+
const bbCss = fs.readFileSync(path.join(bbDir, 'billboard.min.css'), 'utf-8');
|
|
269
|
+
const bbDarkCss = fs.readFileSync(path.join(bbDir, 'theme', 'dark.min.css'), 'utf-8');
|
|
270
|
+
|
|
271
|
+
const styles = transformSync(rawStyles, { loader: 'css', minify: true }).code;
|
|
272
|
+
const appJs = transformSync(rawAppJs, { loader: 'js', minify: true }).code;
|
|
214
273
|
|
|
215
274
|
const escapeForScript = (str) => str
|
|
216
275
|
.replaceAll('</', String.raw`<\u002f`)
|
|
217
276
|
.replaceAll('\u2028', String.raw`\u2028`)
|
|
218
277
|
.replaceAll('\u2029', String.raw`\u2029`);
|
|
219
278
|
|
|
220
|
-
const escapedJson = escapeForScript(
|
|
279
|
+
const escapedJson = escapeForScript(inlineJson);
|
|
221
280
|
const escapedLocale = escapeForScript(JSON.stringify(localeObj));
|
|
222
281
|
|
|
223
282
|
// Use a single-pass replacer to avoid cross-contamination when data payloads
|
|
224
283
|
// contain placeholder-like strings (e.g. __LOCALE_DATA__ inside skill descriptions).
|
|
225
284
|
const placeholders = {
|
|
285
|
+
__BB_CSS__: bbCss,
|
|
286
|
+
__BB_DARK_CSS_STR__: JSON.stringify(bbDarkCss),
|
|
287
|
+
__BB_JS__: bbJs,
|
|
226
288
|
__STYLES__: styles,
|
|
289
|
+
__EN_DATA__: escapeForScript(JSON.stringify(enObj)),
|
|
227
290
|
__LOCALE_DATA__: escapedLocale,
|
|
228
291
|
__APP_JS__: appJs,
|
|
229
292
|
__DATA__: escapedJson,
|
|
293
|
+
__VERSION__: pkg.version,
|
|
230
294
|
};
|
|
231
295
|
const placeholderRe = new RegExp(Object.keys(placeholders).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
232
296
|
const html = template.replace(placeholderRe, (match) => placeholders[match]);
|
|
@@ -394,53 +458,18 @@ async function collectProjectData(configPath, projectPath) {
|
|
|
394
458
|
/**
|
|
395
459
|
* Build task categories by classifying contextNames into work types.
|
|
396
460
|
*
|
|
397
|
-
* Classification is
|
|
398
|
-
* -
|
|
399
|
-
* - New contextNames are auto-classified from their description at build time.
|
|
461
|
+
* Classification is auto-generated at every build:
|
|
462
|
+
* - contextNames are classified from their description using keyword matching.
|
|
400
463
|
* - Built-in tools (context='tool') use a fixed structural mapping.
|
|
464
|
+
* - Category labels come from locale files (taskCat_* keys).
|
|
401
465
|
*/
|
|
402
466
|
const TASK_CAT_FILE = path.join(OUTPUT, 'task-categories.json');
|
|
403
467
|
|
|
404
|
-
//
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
'review': { icon: 'π', ko: 'μ½λ 리뷰', en: 'Code Review' },
|
|
410
|
-
'planning': { icon: 'π', ko: 'μ€κ³ / κ³ν', en: 'Planning & Design' },
|
|
411
|
-
'docs': { icon: 'π', ko: 'λ¬Έμ μμ±', en: 'Documentation' },
|
|
412
|
-
'browser': { icon: 'π', ko: 'λΈλΌμ°μ ', en: 'Browser Automation' },
|
|
413
|
-
'workflow': { icon: 'βοΈ', ko: 'μν¬νλ‘ μλν', en: 'Workflow Automation' },
|
|
414
|
-
'team': { icon: 'π₯', ko: 'ν κ΄λ¦¬', en: 'Team Management' },
|
|
415
|
-
'config': { icon: 'π§', ko: 'μ€μ / μ μ§λ³΄μ', en: 'Config & Maintenance' },
|
|
416
|
-
'general': { icon: 'π¬', ko: 'μΌλ° λν', en: 'General' },
|
|
417
|
-
'other': { icon: 'π¦', ko: 'κΈ°ν', en: 'Other' },
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
// Built-in tool β category (structural, not user data)
|
|
421
|
-
const TOOL_CATEGORY = {
|
|
422
|
-
Edit: 'code-edit', Write: 'code-edit', NotebookEdit: 'code-edit',
|
|
423
|
-
Read: 'code-search', Grep: 'code-search', Glob: 'code-search',
|
|
424
|
-
LSP: 'code-search', ToolSearch: 'code-search', Explore: 'code-search',
|
|
425
|
-
AskUserQuestion: 'code-search',
|
|
426
|
-
Bash: 'execution',
|
|
427
|
-
EnterPlanMode: 'planning', ExitPlanMode: 'planning', Plan: 'planning',
|
|
428
|
-
TaskCreate: 'planning', TaskUpdate: 'planning', TaskOutput: 'planning', TaskStop: 'planning',
|
|
429
|
-
TeamCreate: 'team', TeamDelete: 'team', SendMessage: 'team',
|
|
430
|
-
WebFetch: 'browser', WebSearch: 'browser',
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
// Category keyword seeds β used ONLY for auto-classifying new items
|
|
434
|
-
const CAT_KEYWORDS = {
|
|
435
|
-
'review': ['review', 'lint', 'simplif', '리뷰', 'κ²μ', 'κ²ν '],
|
|
436
|
-
'planning': ['plan', 'design', 'architect', 'brainstorm', 'κ³ν', 'μ€κ³', 'κΈ°ν'],
|
|
437
|
-
'docs': ['doc', 'report', 'meeting', 'wiki', 'publish', 'humaniz', 'summary', 'λ¬Έμ', 'νμ', 'λ³΄κ³ ', 'μ 리', 'μμ½', 'μμ±', 'κΈ°μ 곡μ ', 'κΈ°λ‘'],
|
|
438
|
-
'browser': ['browser', 'chrome', 'scrape', 'page', 'λΈλΌμ°μ '],
|
|
439
|
-
'workflow': ['workflow', 'n8n', 'cron', 'schedule', 'μλν', 'μν¬νλ‘'],
|
|
440
|
-
'team': ['team', 'leader', 'manager', 'ν', 'λ§€λμ '],
|
|
441
|
-
'config': ['config', 'setting', 'setup', 'hook', 'plugin', 'health', 'improve', 'skill-creator', 'μ€μ ', 'κ°μ '],
|
|
442
|
-
'execution':['debug', 'test', 'exec', 'build', 'implement', 'λλ²κ·Έ', 'μ€ν', 'ν
μ€νΈ', 'ꡬν'],
|
|
443
|
-
};
|
|
468
|
+
// Work types loaded from external file (categories, tool mapping, keywords)
|
|
469
|
+
const WORK_TYPES = JSON.parse(fs.readFileSync(path.join(TEMPLATES, 'work-types.json'), 'utf-8'));
|
|
470
|
+
const WORK_TYPE_META = WORK_TYPES.categories;
|
|
471
|
+
const TOOL_CATEGORY = WORK_TYPES.toolMapping;
|
|
472
|
+
const CAT_KEYWORDS = WORK_TYPES.keywords;
|
|
444
473
|
|
|
445
474
|
function buildTaskCategories(scopeData) {
|
|
446
475
|
// 1. Collect descriptions from harness data
|
|
@@ -454,13 +483,7 @@ function buildTaskCategories(scopeData) {
|
|
|
454
483
|
}
|
|
455
484
|
}
|
|
456
485
|
|
|
457
|
-
// 2.
|
|
458
|
-
let persisted = {};
|
|
459
|
-
try {
|
|
460
|
-
persisted = JSON.parse(fs.readFileSync(TASK_CAT_FILE, 'utf-8'));
|
|
461
|
-
} catch { /* first run or missing file */ }
|
|
462
|
-
|
|
463
|
-
// 3. Collect all contextNames from token data
|
|
486
|
+
// 2. Collect all contextNames from token data
|
|
464
487
|
const allNames = new Set();
|
|
465
488
|
for (const sd of Object.values(scopeData)) {
|
|
466
489
|
for (const e of (sd.usage?.tokenEntries || [])) {
|
|
@@ -468,7 +491,7 @@ function buildTaskCategories(scopeData) {
|
|
|
468
491
|
}
|
|
469
492
|
}
|
|
470
493
|
|
|
471
|
-
//
|
|
494
|
+
// 3. Auto-classify each contextName
|
|
472
495
|
function autoClassify(name, contextType) {
|
|
473
496
|
if (contextType === 'tool' && TOOL_CATEGORY[name]) return TOOL_CATEGORY[name];
|
|
474
497
|
if (contextType === 'general') return 'general';
|
|
@@ -485,22 +508,17 @@ function buildTaskCategories(scopeData) {
|
|
|
485
508
|
return bestCat || 'other';
|
|
486
509
|
}
|
|
487
510
|
|
|
488
|
-
//
|
|
511
|
+
// 4. Build mapping: auto-classify all contextNames
|
|
489
512
|
const mapping = {};
|
|
490
513
|
for (const sd of Object.values(scopeData)) {
|
|
491
514
|
for (const e of (sd.usage?.tokenEntries || [])) {
|
|
492
515
|
const name = e.contextName || 'conversation';
|
|
493
516
|
if (mapping[name]) continue;
|
|
494
|
-
|
|
495
|
-
if (persisted[name]) {
|
|
496
|
-
mapping[name] = persisted[name];
|
|
497
|
-
} else {
|
|
498
|
-
mapping[name] = autoClassify(name, e.context || 'general');
|
|
499
|
-
}
|
|
517
|
+
mapping[name] = autoClassify(name, e.context || 'general');
|
|
500
518
|
}
|
|
501
519
|
}
|
|
502
520
|
|
|
503
|
-
//
|
|
521
|
+
// 5. Save mapping for reference
|
|
504
522
|
const sorted = {};
|
|
505
523
|
for (const key of Object.keys(mapping).sort()) sorted[key] = mapping[key];
|
|
506
524
|
fs.writeFileSync(TASK_CAT_FILE, JSON.stringify(sorted, null, 2), 'utf-8');
|