Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docs/herdr-insights-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,18 @@ and `insights.json`; the automated installer tests cover those boundaries.
## Sharing a build

Keep `herdr-insights/` self-contained, with `RECIPE.md`, `INSTALL.md`, `README.md`,
`preview.png`, and `mods/insights/`. Do not ship layouts, key snippets, developer
tests/fixtures, or smoke-test tooling in that folder. The installer copies only
the mod tree, generates the local Node path, and records ownership separately.
`preview.png`, `mods/insights/`, and `mods/insights-launcher/`. Do not ship layouts,
key snippets, developer tests/fixtures, or smoke-test tooling in that folder.
The installer preflights both mod trees, checks temporary buffered copies before
replacing either live mod, generates the dashboard's local Node path, and records
ownership separately for each mod. Install/uninstall share an exclusive lock;
interrupted operations require confirming the process stopped before removing it.
The toolbar is a personal layout clone wrapping Herdr's original `tabs` block;
no built-in mod source is cloned, patched, or vendored. See
[INSTALL.md](../herdr-insights/INSTALL.md) for the clone, click-state, and
ownership rules. A clone does not inherit future structural changes to its
source layout. Validate both mods with `herdr-app check` and inspect the actual
toolbar in the existing window; do not launch a second instance.

For Herdr Community discovery, publish the folder in a public GitHub repo and
add topic `herdr-mod` after merging to the default branch. Allow up to an hour for
Expand Down
4 changes: 2 additions & 2 deletions herdr-insights/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ coverage/
.artifacts/

# Herdr rewrites these generated files on start; never commit or install them.
mods/insights/**/*.d.ts
mods/insights/**/jsconfig.json
mods/**/*.d.ts
mods/**/jsconfig.json
*.error
39 changes: 17 additions & 22 deletions herdr-insights/INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
# Install Insights

Requires Herdr and **Node.js 22.15+**. Reuse an existing checkout when available.
Requires **Node.js 22.15+** and a running Herdr. Never launch a second Herdr instance.
Follow Herdr's `AGENTS.md`; this guide does not override its review requirements.
`RECIPE.md` describes rebuilding, not installation.
Use the existing build, not `RECIPE.md` (rebuilding).

## Install

From the parent of `herdr-insights/`:
From the parent of `herdr-insights/`; no `npm install` needed:

```sh
npm run install:local --prefix herdr-insights
```

Only that folder is needed; no `npm install` is required. The installer copies
the mod, records ownership, configures the local Node path, and runs
`herdr-app check`. Existing usage data, layouts, keys, and terminals stay intact.
App home defaults to `$HERDR_APP_HOME` or `~/.config/herdr-app`.
The installer checks temporary copies of both mods before replacing live files.
First install creates/selects `<current-layout>-insights` (suffixing collisions)
and adds **Insights** beside Files/Changes. Reinstall updates owned mods without
rewriting layouts or changing the selected layout. Shipped files, tabs, terminals,
keys, click state, and usage data are preserved.

Finish when the installer exits successfully with **`herdr-app check passed`**.
Report failures and stop; do not force overwrites or rebuild to bypass errors.
Do not run developer tests or change tabs/focus during installation. Never
launch a second Herdr instance.
Success requires two `herdr-app check passed` messages **and exit code 0**.
Do not run developer tests or change tabs during installation. Open the dashboard
only if requested: click **Insights**, or add `-- --open` to the install command.

## Open only if requested
## Errors

```sh
herdr-app do tab.new '{"name":"Insights","mod":"insights"}'
```

## Uninstall

Usage data and locally edited/unowned files are preserved:

```sh
npm run uninstall:local --prefix herdr-insights
```
Stop and report; do not force overwrites or rebuild to bypass errors. The layout
must contain the native `tabs` block. Preserve local edits and any failed layout
clone. Remove the app home's `.insights-install.lock` directory only after
confirming no installer/uninstaller is running.
23 changes: 15 additions & 8 deletions herdr-insights/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ cd coding-agent-toolkit
npm run install:local --prefix herdr-insights
```

The installer runs `herdr-app check` without opening a tab or test window.
Follow [INSTALL.md](INSTALL.md) for the installation steps. Add `-- --open` only
if you want it to open one **Insights** tab. It installs owned files only under
`mods/insights/` and writes its install manifest to `data/insights/` in
`~/.config/herdr-app`; your layouts, keys, and terminals are unchanged. Set `HERDR_APP_HOME` to use a different app home.
With Herdr running, the installer checks both mods and adds **Insights** beside
**Files / Changes**; clicking it reuses the current space's Insights tab, opening
one only when absent. It installs
`mods/insights/` and `mods/insights-launcher/`, records ownership in
`data/insights/`, and creates a personal layout clone for the button. Shipped
files, keys, terminals, and usage data are unchanged. See [INSTALL.md](INSTALL.md)
for the layout and click-state rules. Set `HERDR_APP_HOME` to use a different app home.

## Use

Expand All @@ -46,14 +48,19 @@ response text or modify source transcripts.
## Development

[RECIPE.md](RECIPE.md) is the self-contained rebuild brief required by Herdr.
The distribution folder is `herdr-insights`; its native mod ID is `insights`
and its display name is **Insights**. No layouts or key bindings ship.
The distribution folder is `herdr-insights`; its dashboard mod ID is `insights`
and its display name is **Insights**. `insights-launcher` is a separate, native
toolbar button that shows the space's existing Insights tab, opening one only
when absent; it has no polling, helper process, or overlays. No layouts or key
bindings ship; the button lives in a personal layout clone composed by the
installer (see [INSTALL.md](INSTALL.md)).

Developer tests, fixtures, and release validation live outside this distribution;
see the repo's [development guide](../docs/herdr-insights-development.md). They are
not installation steps.

Uninstall while keeping usage data:
With Herdr running, uninstall while keeping usage data and returning to the
original layout:

```sh
npm run uninstall:local --prefix herdr-insights
Expand Down
91 changes: 71 additions & 20 deletions herdr-insights/RECIPE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,57 @@

A native Herdr dashboard for token usage across Pi, Codex, Claude Code, and
OpenCode. It reads existing local session data, shows model rankings and activity,
and opens in its own tab without changing terminals, layouts, or keys.
and opens in its own tab from an Insights button beside Files and Changes. No
built-in Herdr code, terminals, or keys are changed.

![Insights](preview.png)

Installing this existing build? Follow [INSTALL.md](INSTALL.md) and the user's
Herdr `AGENTS.md`. This recipe is a rebuild brief; its **Done when** checks apply
to building/releasing, not routine installation. Installation does not launch
smoke-test windows or open a tab unless requested.
smoke-test windows or open a dashboard tab unless requested. First installation
selects a personal layout with the button in the existing toolbar row.

**What you see:** A vertically scrollable dashboard centered at a maximum width
of 1180px. Use Herdr theme colors, 1px borders, 8px card corners, 6px control
corners, no decorative shadows, and 4/8/12/16/24px spacing. Page padding is 12px
below 620px, 16px below 900px, and 24px otherwise. Support light and dark themes.

Top to bottom:
A separate `insights-launcher` mod draws the **Insights** toolbar button after
Files and Changes. Match Herdr's panel controls: 26px height, 8px horizontal
padding, 5px icon/text gap, 6px corners, 12px type, 14px theme-tinted chart icon,
chrome background, muted text, and native hover colors. Its root matches the tab
bar's chrome background and 1px bottom border. Stop mouse-down propagation on
the button so it does not drag the window. Every click shows the current space's
existing Insights tab (the tab whose pane runs the `insights` mod) instead of
stacking duplicates. Focus its pane with `pane.focus`; if a different pane in
that tab is zoomed, unzoom it first so the dashboard is visible. It calls `tab.new` with `{name: "Insights", mod: "insights"}`
only when that space has none. No floating window, overlay, polling, file access,
or helper process.

Dashboard, top to bottom:
- An Insights header with last-update status, Refresh, and Settings.
- Five all-time metrics: tokens, peak-day tokens, longest task, longest streak,
and current streak. Use 22px values and small uppercase labels; unknown duration
is a dash. The strip wraps from five columns to three or two on smaller views.
- Token usage: a 53-week, seven-row local-calendar heatmap with month/weekday
labels and Daily / Weekly / Cumulative controls. Cells are 12–20px squares
fitted to available width, with 2px gaps; narrow views scroll horizontally.
Future days are dimmed. Clicking a day updates the detail card.
fitted to available width, with 2px gaps; when even 12px cells cannot fit, the
grid overflows and stays aligned to the newest weeks. Future days are dimmed.
Clicking a day updates the detail card.
- Side-by-side all-time and selected-day details: input, output, cache-hit rate,
and top model. Default the selected day to today.
- Model usage: Week / Month / Year / All controls, a year stepper for Year,
Provider and Model menus, and Clear filters when a filter is active.
- Top models: a 26-week stacked token chart with date labels, a token axis, and
a categorical legend. Keep seven model series and fold the rest into Other.
- Model Usage: the top ten models with provider attribution, compact token totals,
and change versus the previous calendar period. Use two columns at 960px+, one
below. Show a known model logo, else a provider logo, else an initial. Up/down
arrows and theme success/destructive colors distinguish trends; handle New and
no-comparison states. Rows do not expand.
and change versus the previous calendar period. Two columns of five that wrap to
one when the pane cannot hold both; use a 300px flex basis, growth, and zero
minimum width so a column can shrink inside narrow splits. Show a known model
logo, else a provider logo, else an initial. Up/down arrows and theme
success/destructive colors distinguish trends; handle New and no-comparison
states. Rows do not expand.

Settings replaces the dashboard content. It contains palette (blue/green/purple),
week start, include-cache, refresh interval, per-source toggles and root editors,
Expand All @@ -58,12 +75,32 @@ controls preserve custom mappings. Changing source configuration invalidates the
scan cache; palette/cache/week-start/refresh changes preserve collected history.

**Built from:** Put this distribution in `herdr-insights/`, with `RECIPE.md`,
`README.md`, `INSTALL.md`, `preview.png`, and `mods/insights/`. Keep developer
`README.md`, `INSTALL.md`, `preview.png`, `mods/insights/`, and
`mods/insights-launcher/`. Keep developer
tests, fixtures, and release-check tooling outside this distribution folder.
Do not include a `layouts/` folder or keys snippet: no bindings are needed.
The recipe heading and `mod.json` title are **Insights**; the native mod ID is `insights`.

`mods/insights/mod.json` contains `{"place":"none","title":"Insights"}`.
`mods/insights-launcher/mod.json` also uses `place: "none"`, `title: "Insights"`.
Its `main.js` default-exports a small native View using gpui-base flex builders,
`theme()`, and `run()`; bundle its chart icon as `insights.svg`.

`scripts/toolbar.mjs` integrates through layout composition, not source patching.
Use `layout.clone {name: current, as: current + "-insights"}` to preserve Herdr's
appearance/click state. Before cloning, reject symlinked source state or `.state`
parents; choose a numeric suffix if either the destination layout or its state
already exists. Never overwrite another layout's state, including leftover state
from uninstall. Do not edit or remove Herdr-managed click state. In the personal clone only, turn the existing `tabs`
block into a nonresizable row, keeping its ID, height, paint, and window-drag
attributes. Its first child is a weight-1 block with the original `content`
(including its props); its second is a 104px block with `insights-launcher`.
Do not copy or modify the shipped `tabs` mod. Refuse unsupported/ambiguous layouts.
Record the personal layout's name, source, and hash in `toolbar-install.json`.
Reinstall must not rewrite this layout or switch the user's selected layout.
Uninstall validates the original layout and confirms it actually became selected
before deleting an active, unchanged owned clone. Locally edited clones, missing
originals, or unsuccessful switching block removal before deleting any files.
`main.js` default-exports a gpui-kit `View`; use gpui-base flex/controls, `theme()`
semantic colors, `on_size_change`, and fresh callback contexts. Do not retain `cx`.
Cancel tasks/timers and release native editor/input state on close. Use native
Expand Down Expand Up @@ -99,13 +136,21 @@ and executable mappings. `core/config.mjs` defines and validates the full schema

Provide dependency-free Node install/uninstall scripts. The installer requires
Node >=22.15 and validates it before writing anything; the uninstaller intentionally
has no Node gate so it remains usable on older runtimes. Install only the mod tree
(plus its manifest under `data/insights/`) and generate `runtime.json` with the
has no Node gate so it remains usable on older runtimes. Install only these two
mod trees (plus ownership manifests under `data/insights/`) and generate the dashboard's `runtime.json` with the
absolute installer Node path; the UI uses that path, falling back to `node` on PATH
only for manual copies. Track ownership/hashes in a separate install manifest.
Refuse symlinks and unowned collisions; refuse edited owned files unless explicitly
forced. Preserve usage data and unrelated files. Run `herdr-app check` and, only
with `--open`, `herdr-app do tab.new '{"name":"Insights","mod":"insights"}'`.
Keep the existing dashboard manifest compatible and use
`launcher-install-manifest.json` for the launcher. Preflight both targets before
writing either. Refuse symlinks and unowned collisions; refuse edited owned files
unless explicitly forced. Preserve usage data and unrelated files. Check buffered
copies of both mods with `herdr-app check` in a temporary app home before replacing
either live mod; clean up on success or failure. Revalidate targets after checking
to preserve edits made during validation. Serialize install/uninstall with an
exclusive `.insights-install.lock` directory for the full operation, and never
steal a lock automatically. Recheck clone destinations and source state just before
cloning, and recheck the active layout and clone hash before deleting on uninstall.
Keep Herdr running for toolbar installation/removal; never start a second instance.

**Watch out:** Canonical total is input + output + cacheRead + cacheWrite;
input is uncached, output already includes reasoning. Never add reasoning twice.
Expand All @@ -118,17 +163,23 @@ explicit rebuild after same-size rewrites. Use ownership-checked store locks;
never steal live/unknown owners' locks based on age. Preserve corrupt stores with
a backup before rebuilding; read-only prepare must not repair or create stores.

Never write layouts, keys, source transcripts, or an existing user's usage data
from the installer. Do not copy an absolute runtime path from another machine.
Never modify shipped layouts/mods, keys, source transcripts, or existing usage
data. Write only the owned personal layout clone. Preserve collisions and local
edits. Do not copy an absolute runtime path from another machine.

**Done when:** For a new build or release (not an installation), package tests
and checks pass, including distribution naming, PNG preview, recipe structure,
no layouts, token accounting, deduplication, configuration validation, incremental
recovery, and safe installation/uninstallation.
Run `herdr-app check` on the installed mod, plus populated-render and asynchronous
Run `herdr-app check` on both installed mods. Inspect a real screenshot: Insights
must be in the same row as Files/Changes with matching button styling, no extra
strip or floating window. Clicks reuse the current space's dashboard, including
when it is in a split behind another pane's zoom, without creating duplicates.
Confirm built-in
code/layouts are unchanged and reinstall does not duplicate the toolbar. Also run populated-render and asynchronous
native smoke checks in temporary app homes. Inspect a native screenshot and status
for render errors. Verify actual filters, heatmap modes, source settings, refresh,
and empty/error states. Reinstall without changing source transcripts, layouts,
keys, local edits, or `insights.json`. Keep the GitHub repo public and add topic
and empty/error states. Reinstall without changing source transcripts, existing
layouts, keys, local edits, or `insights.json`. Keep the GitHub repo public and add topic
`herdr-mod` for Community discovery after merging to the default branch; allow
up to an hour for indexing and at least 24 hours for picture screening.
1 change: 1 addition & 0 deletions herdr-insights/mods/insights-launcher/insights.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions herdr-insights/mods/insights-launcher/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Composed beside Herdr's original tabs mod; never copies or patches its code.
import { View, svg } from "gpui-kit";
import { h_flex } from "gpui-base";
import { panes, run, spaces, theme } from "herdr";

const TAB = { name: "Insights", mod: "insights" };

/** One Insights tab per space: show it if it exists, otherwise create it. */
function showInsights() {
const active = spaces().find((space) => space.active);
const tabIds = new Set(active?.tabs.map((tab) => tab.id) ?? []);
const open = panes().find((pane) => pane.mod === TAB.mod && tabIds.has(pane.tab));
if (open) {
// Selecting a tab alone keeps its previous focus/zoom, which may hide this
// pane behind a terminal. Reveal the dashboard without changing split sizes.
const zoomed = panes().find((pane) => pane.tab === open.tab && pane.zoomed);
if (zoomed && zoomed.pane !== open.pane) run("pane.zoom", { pane: zoomed.pane });
return run("pane.focus", { pane: open.pane });
}
return run("tab.new", TAB);
}

export default class InsightsLauncher extends View {
render() {
const t = theme();
return h_flex().size_full().pr(10).items_center()
.bg(t.chrome).border_b(1).border_color(t.border)
.child(h_flex().id("insights-open").h(26).px(8).gap(5).flex_none()
.items_center().rounded(6).text_size(12).bg(t.chrome).text_color(t.muted)
.hover((style) => style.bg(t.hover).text_color(t.foreground))
.on_mouse_down("left", (_event, cx) => cx.stop_propagation())
.on_click(() => showInsights())
.child(svg("mods/insights-launcher/insights.svg").size(14))
.child("Insights"));
}
}
1 change: 1 addition & 0 deletions herdr-insights/mods/insights-launcher/mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "place": "none", "title": "Insights" }
Loading
Loading