@tmustier/pi-usage-extension 0.7.0 → 0.7.1

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-07-17
4
+
5
+ ### Added
6
+ - Descriptive loading messages with a live file counter while `/usage` opens: “Building your usage history for the first time…” on a fresh install, “Rebuilding your usage history — the cache format changed…” after an upgrade that bumps the cache version, and “Updating your usage history since \<date>…” for routine incremental refreshes. Warm opens with nothing to parse keep the plain spinner.
7
+ - `collectUsageData` accepts an `onProgress` callback reporting mode (`first-run` / `rebuild` / `update`), files to parse, files parsed, and the newest already-ingested session activity timestamp.
8
+
3
9
  ## [0.7.0] - 2026-07-17
4
10
 
5
11
  ### Changed
package/README.md CHANGED
@@ -159,7 +159,7 @@ On narrow terminals, `/usage` automatically switches to a compact table instead
159
159
  - **On-disk cache.** Per-file extraction results are cached in `<agentDir>/usage-extension-cache.json` (respects `PI_CODING_AGENT_DIR`), keyed by file size + mtime. Warm opens only re-parse session files that changed since the last run — on a 5.2 GB / 3,310-file corpus that takes the open from ~17 s to ~0.3 s.
160
160
  - **First open** after install (or after deleting the cache) does a one-off full build, showing the usual cancellable loader. Cancelling saves partial progress, so the next open resumes where it left off.
161
161
  - The cache is safe to delete at any time; it is rebuilt automatically. Corrupt or version-mismatched caches are ignored and rebuilt rather than trusted.
162
- - **0.7.0 bumps the cache format to v3** (adds session working directory and compaction markers; 0.6.0's v2 added thinking level and reasoning tokens). The first open after upgrading does a one-off full rebuild, then warm opens are fast again.
162
+ - **0.7.0 bumps the cache format to v3** (adds session working directory and compaction markers; 0.6.0's v2 added thinking level and reasoning tokens). The first open after upgrading does a one-off full rebuild (with a progress message and live file counter), then warm opens are fast again.
163
163
 
164
164
  ## Provider Notes
165
165
 
package/data.ts CHANGED
@@ -619,6 +619,7 @@ function getPeriodsForTimestamp(
619
619
  }
620
620
 
621
621
  const DAY_MS = 24 * HOUR_MS;
622
+ const PROGRESS_REPORT_EVERY = 100;
622
623
 
623
624
  function addMessagesToUsageData(
624
625
  data: UsageData,
@@ -727,8 +728,21 @@ const STAT_CONCURRENCY = 16;
727
728
  const DEFAULT_PARSE_CONCURRENCY = 4;
728
729
  const AGGREGATE_YIELD_EVERY_FILES = 200;
729
730
 
731
+ export interface CollectProgress {
732
+ /** Why this pass needs to parse files. */
733
+ mode: "first-run" | "rebuild" | "update";
734
+ /** Session files that need parsing this pass (0 = fully warm). */
735
+ filesToParse: number;
736
+ /** Files parsed so far; reported in coarse increments. */
737
+ filesParsed: number;
738
+ /** Newest session activity already ingested (ms since epoch); null when starting fresh. */
739
+ sinceMs: number | null;
740
+ }
741
+
730
742
  export interface CollectUsageOptions {
731
743
  signal?: AbortSignal;
744
+ /** Called once before parsing begins and periodically while files are parsed. */
745
+ onProgress?: (progress: CollectProgress) => void;
732
746
  /** Defaults to `<agentDir>/sessions`. */
733
747
  sessionsDir?: string;
734
748
  /** Defaults to `<agentDir>/usage-extension-cache.json`. Pass `null` to disable the on-disk cache. */
@@ -794,6 +808,15 @@ export async function collectUsageData(options: CollectUsageOptions = {}): Promi
794
808
  if (signal?.aborted) return null;
795
809
 
796
810
  // 3. Load the cache and decide which files actually need parsing.
811
+ let cacheFileExists = false;
812
+ if (cachePath) {
813
+ try {
814
+ await stat(cachePath);
815
+ cacheFileExists = true;
816
+ } catch {
817
+ // No cache file yet — first run.
818
+ }
819
+ }
797
820
  const previous = cachePath ? await loadUsageCache(cachePath) : new Map<string, CachedFileState>();
798
821
  if (signal?.aborted) return null;
799
822
  const current = new Map<string, CachedFileState>();
@@ -818,6 +841,22 @@ export async function collectUsageData(options: CollectUsageOptions = {}): Promi
818
841
  }
819
842
  }
820
843
 
844
+ // Progress reporting: distinguish a true first run from a format-change
845
+ // rebuild (cache file present but unusable) and a routine incremental update.
846
+ const progressMode: CollectProgress["mode"] =
847
+ previous.size > 0 ? "update" : cacheFileExists ? "rebuild" : "first-run";
848
+ let sinceMs: number | null = null;
849
+ if (progressMode === "update") {
850
+ for (const state of previous.values()) {
851
+ if (state.mtimeMs > (sinceMs ?? 0)) sinceMs = state.mtimeMs;
852
+ }
853
+ }
854
+ let filesParsed = 0;
855
+ const reportProgress = (): void => {
856
+ options.onProgress?.({ mode: progressMode, filesToParse: toParse.length, filesParsed, sinceMs });
857
+ };
858
+ reportProgress();
859
+
821
860
  // 4. Parse new/changed files with bounded concurrency.
822
861
  {
823
862
  let next = 0;
@@ -831,11 +870,16 @@ export async function collectUsageData(options: CollectUsageOptions = {}): Promi
831
870
  try {
832
871
  buffer = await readFile(filePath);
833
872
  } catch {
873
+ filesParsed++;
834
874
  continue; // File vanished — skip it.
835
875
  }
836
876
  const parsed = await parseSessionBuffer(buffer, signal);
837
877
  if (signal?.aborted) return; // Never cache a partial parse.
838
878
  current.set(filePath, { size: st.size, mtimeMs: st.mtimeMs, parsed });
879
+ filesParsed++;
880
+ if (filesParsed % PROGRESS_REPORT_EVERY === 0 || filesParsed === toParse.length) {
881
+ reportProgress();
882
+ }
839
883
  }
840
884
  })
841
885
  );
package/index.ts CHANGED
@@ -14,6 +14,7 @@ import { DynamicBorder } from "@earendil-works/pi-coding-agent";
14
14
  import { CancellableLoader, Container, Spacer, matchesKey, visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
15
15
 
16
16
  import { collectUsageData, TAB_ORDER } from "./data";
17
+ import type { CollectProgress } from "./data";
17
18
  import type { BaseStats, TabName, UsageData } from "./data";
18
19
  import {
19
20
  buildGraphModel,
@@ -175,6 +176,18 @@ function seriesColor(index: number): string {
175
176
  return SERIES_COLORS[index % SERIES_COLORS.length]!;
176
177
  }
177
178
 
179
+ /** "14:32" if the timestamp is today, otherwise "16 Jul" (with year if not this year). */
180
+ function formatSinceDate(ms: number): string {
181
+ const d = new Date(ms);
182
+ const now = new Date();
183
+ if (d.toDateString() === now.toDateString()) {
184
+ return d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
185
+ }
186
+ const opts: Intl.DateTimeFormatOptions = { day: "numeric", month: "short" };
187
+ if (d.getFullYear() !== now.getFullYear()) opts.year = "numeric";
188
+ return d.toLocaleDateString(undefined, opts);
189
+ }
190
+
178
191
  function padLeft(s: string, len: number): string {
179
192
  const vis = visibleWidth(s);
180
193
  if (vis >= len) return s;
@@ -722,7 +735,20 @@ export default function (pi: ExtensionAPI) {
722
735
 
723
736
  loader.onAbort = () => finish(null);
724
737
 
725
- collectUsageData({ signal: loader.signal })
738
+ const onProgress = (p: CollectProgress): void => {
739
+ if (finished || p.filesToParse === 0) return;
740
+ const files = `${p.filesParsed.toLocaleString()}/${p.filesToParse.toLocaleString()} files`;
741
+ if (p.mode === "update") {
742
+ const since = p.sinceMs !== null ? ` since ${formatSinceDate(p.sinceMs)}` : "";
743
+ loader.setMessage(`Updating your usage history${since}… (${files})`);
744
+ } else if (p.mode === "rebuild") {
745
+ loader.setMessage(`Rebuilding your usage history — the cache format changed… (${files})`);
746
+ } else {
747
+ loader.setMessage(`Building your usage history for the first time… (${files})`);
748
+ }
749
+ };
750
+
751
+ collectUsageData({ signal: loader.signal, onProgress })
726
752
  .then(finish)
727
753
  .catch(() => finish(null));
728
754
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-usage-extension",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Usage statistics dashboard for Pi sessions.",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Mustier",