@narumitw/pi-analytics 0.49.5 → 0.49.6

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/menu.ts +49 -19
package/README.md CHANGED
@@ -96,7 +96,7 @@ Pi exposes HTTP responses and final assistant failures, not every provider-SDK t
96
96
 
97
97
  The command accepts no arguments. TUI mode uses the full dashboard; RPC mode adapts the same standard screens to dialogs. Print and JSON modes reject the interactive command observably instead of writing ad hoc protocol output.
98
98
 
99
- The root menu contains Change time range, Skills, Tools, Provider reliability, Response cycles, Data & privacy, and Close. Skills and Tools are searchable browse views with details and model breakdowns. Escape goes Back from nested screens and closes the root. Ctrl+C closes the menu. Cancelling data deletion has no side effects.
99
+ The root menu contains Change time range, Skills, Tools, Provider reliability, Response cycles, Data & privacy, and Close. Skills and Tools are searchable browse views with details and model breakdowns. Escape goes Back from nested screens and closes the root. Ctrl+C closes the menu. Data deletion uses Pi TUI Kit's standalone confirmation: Back keeps the dashboard open, Ctrl+C closes it in TUI mode, and cancellation never clears data.
100
100
 
101
101
  ## 🔐 Local data and privacy
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-analytics",
3
- "version": "0.49.5",
3
+ "version": "0.49.6",
4
4
  "description": "Local-first usage analytics for Pi models, skills, tools, and reliability.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "dependencies": {
35
- "@narumitw/pi-tui-kit": "^0.49.1"
35
+ "@narumitw/pi-tui-kit": "^0.51.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-coding-agent": "*"
package/src/menu.ts CHANGED
@@ -28,6 +28,11 @@ export interface AnalyticsMenuState {
28
28
  result: AnalyticsLoadResult;
29
29
  }
30
30
 
31
+ export interface AnalyticsMenuOptions {
32
+ runConfirmation: typeof import("@narumitw/pi-tui-kit")["runConfirmation"];
33
+ isCurrent(): boolean;
34
+ }
35
+
31
36
  type Screen = "main" | "range" | "skills" | "tools" | "reliability" | "responses" | "privacy";
32
37
  type Action = "setRange" | "clearData";
33
38
 
@@ -38,7 +43,11 @@ const RANGE_LABELS: Record<TimeRangeId, string> = {
38
43
  all: "All time",
39
44
  };
40
45
 
41
- export function createAnalyticsMenu(source: AnalyticsMenuDataSource, now: () => number = Date.now) {
46
+ export function createAnalyticsMenu(
47
+ source: AnalyticsMenuDataSource,
48
+ now: () => number = Date.now,
49
+ options?: AnalyticsMenuOptions,
50
+ ) {
42
51
  let rangeId: TimeRangeId = "7d";
43
52
  let cachedState: AnalyticsMenuState | undefined;
44
53
  const loadState = async (signal: AbortSignal): Promise<AnalyticsMenuState> => {
@@ -119,27 +128,45 @@ export function createAnalyticsMenu(source: AnalyticsMenuDataSource, now: () =>
119
128
  },
120
129
  clearData: async ({ ctx, state, signal }) => {
121
130
  if (state.result.kind !== "ready") return { kind: "stay" };
131
+ if (!options) throw new Error("Analytics confirmation is unavailable");
122
132
  const count = state.result.snapshot.overview.responseCycles;
123
- const confirmed = await ctx.ui.confirm(
124
- "Delete analytics data?",
125
- `This will clear all local analytics history from:\n\n${safeDisplayText(state.path)}\n\nThe selected range currently shows ${count} response cycles. Other running Pi processes may add new records afterward.`,
126
- { signal },
127
- );
128
- if (!confirmed || signal.aborted) return { kind: "stay" };
133
+ const confirmation = await options.runConfirmation(ctx, {
134
+ title: "Delete analytics data?",
135
+ message: `This will clear all local analytics history from:\n\n${safeDisplayText(state.path)}\n\nThe selected range currently shows ${count} response cycles. Other running Pi processes may add new records afterward.`,
136
+ confirmLabel: "Delete data",
137
+ cancelLabel: "Keep data",
138
+ signal,
139
+ isCurrent: options.isCurrent,
140
+ // Keep the dashboard's existing domain-level error route as the only notifier.
141
+ onError: () => undefined,
142
+ });
143
+ if (signal.aborted || !options.isCurrent()) return { kind: "close" };
144
+ if (confirmation.kind === "closed") {
145
+ return confirmation.reason === "close" ? { kind: "close" } : { kind: "stay" };
146
+ }
147
+ if (confirmation.kind === "stale") return { kind: "close" };
148
+ if (confirmation.kind === "unsupported") {
149
+ throw new Error(`Analytics confirmation is unavailable in ${confirmation.mode} mode`);
150
+ }
151
+ if (confirmation.kind === "error") throw confirmation.error;
152
+
129
153
  const result = await source.clearAll(signal);
130
154
  cachedState = undefined;
131
- try {
132
- ctx.ui.notify("Cleared local analytics data.", "info");
133
- if (result.cleanupIncomplete) {
134
- ctx.ui.notify(
135
- "Some obsolete analytics files are still in use. Stop other Pi processes and clear again to remove them.",
136
- "warning",
137
- );
155
+ const isCurrent = options.isCurrent();
156
+ if (isCurrent) {
157
+ try {
158
+ ctx.ui.notify("Cleared local analytics data.", "info");
159
+ if (result.cleanupIncomplete) {
160
+ ctx.ui.notify(
161
+ "Some obsolete analytics files are still in use. Stop other Pi processes and clear again to remove them.",
162
+ "warning",
163
+ );
164
+ }
165
+ } catch {
166
+ // The host can dispose the current UI after the ownership check.
138
167
  }
139
- } catch {
140
- // Session replacement can invalidate the UI after the generation switch.
141
168
  }
142
- return signal.aborted ? { kind: "close" } : { kind: "to", screen: "main" };
169
+ return signal.aborted || !isCurrent ? { kind: "close" } : { kind: "to", screen: "main" };
143
170
  },
144
171
  },
145
172
  };
@@ -158,9 +185,12 @@ export async function showAnalyticsMenu(
158
185
  source: AnalyticsMenuDataSource,
159
186
  options: { signal: AbortSignal; isCurrent: () => boolean },
160
187
  ): Promise<void> {
161
- const { runMenu, runTask } = await import("@narumitw/pi-tui-kit");
188
+ const { runConfirmation, runMenu, runTask } = await import("@narumitw/pi-tui-kit");
162
189
  if (options.signal.aborted || !options.isCurrent()) return;
163
- const controller = createAnalyticsMenu(source);
190
+ const controller = createAnalyticsMenu(source, Date.now, {
191
+ runConfirmation,
192
+ isCurrent: options.isCurrent,
193
+ });
164
194
  const loading = await runTask(ctx, {
165
195
  label: "Loading local analytics…",
166
196
  signal: options.signal,