@tmustier/pi-usage-extension 0.9.0 → 0.9.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.9.1] - 2026-07-17
4
+
5
+ ### Changed
6
+ - Exports now save to `/tmp` (or the OS temp dir where `/tmp` doesn't exist) instead of the pi session's cwd, so they never litter a repo or home directory. The `✓ Saved` note shows the full path.
7
+ - The export directory is configurable: `{ "usage-extension": { "exportDir": "~/Downloads" } }` in `~/.pi/agent/settings.json` (`~` expands; the directory is created if missing).
8
+
3
9
  ## [0.9.0] - 2026-07-17
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -7,7 +7,7 @@ A Pi extension that displays aggregated usage statistics across all sessions.
7
7
  ## Compatibility
8
8
 
9
9
  - **Pi version:** 0.42.4+
10
- - **Last updated:** 2026-07-17 (0.9.0)
10
+ - **Last updated:** 2026-07-17 (0.9.1)
11
11
 
12
12
  ## Installation
13
13
 
@@ -107,7 +107,7 @@ A note on reading them: cache-miss and context numbers are **observed cost, not
107
107
 
108
108
  ### Export
109
109
 
110
- Press `e` in any view to write the **current slice** to the current working directory:
110
+ Press `e` in any view to write the **current slice** to `/tmp` (or the OS temp dir where `/tmp` doesn't exist) — exports never litter your repo or home directory, and the `✓ Saved` note shows the full path so you can grab the file if you want to keep it:
111
111
 
112
112
  | View | File | Contents |
113
113
  |---|---|---|
@@ -117,6 +117,14 @@ Press `e` in any view to write the **current slice** to the current working dire
117
117
 
118
118
  A `✓ Saved …` note confirms the write (or reports the error) above the help line.
119
119
 
120
+ To export somewhere permanent instead, set an export directory in `~/.pi/agent/settings.json` (created if missing, `~` expands):
121
+
122
+ ```json
123
+ {
124
+ "usage-extension": { "exportDir": "~/Downloads" }
125
+ }
126
+ ```
127
+
120
128
  ### Graph explorer
121
129
 
122
130
  The **Graphs** view plots usage over time for the active period as a braille line chart, with a legend showing per-series totals and shares.
package/data.ts CHANGED
@@ -198,7 +198,7 @@ export interface ParsedSessionFile {
198
198
  // Paths
199
199
  // =============================================================================
200
200
 
201
- function getAgentDir(): string {
201
+ export function getAgentDir(): string {
202
202
  // Replicate Pi's logic: respect PI_CODING_AGENT_DIR env var
203
203
  return process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
204
204
  }
package/export.ts CHANGED
@@ -7,9 +7,43 @@
7
7
  * component owns file naming and disk writes.
8
8
  */
9
9
 
10
+ import { join } from "node:path";
10
11
  import type { Insight, ProviderStats, TotalStats } from "./data.ts";
11
12
  import type { GraphModel } from "./graph.ts";
12
13
 
14
+ /**
15
+ * Read the configured export directory from settings.json content, if any.
16
+ * Config shape: `{ "usage-extension": { "exportDir": "~/Downloads" } }`.
17
+ */
18
+ export function parseExportDirSetting(settingsJson: string): string | null {
19
+ try {
20
+ const parsed = JSON.parse(settingsJson) as { "usage-extension"?: { exportDir?: unknown } };
21
+ const dir = parsed["usage-extension"]?.exportDir;
22
+ return typeof dir === "string" && dir.trim() !== "" ? dir.trim() : null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Pick the export directory. A configured dir wins (with `~` expanded);
30
+ * otherwise exports go to /tmp so they never litter a repo or home
31
+ * directory, falling back to the OS temp dir where /tmp doesn't exist.
32
+ */
33
+ export function resolveExportDir(
34
+ configured: string | null,
35
+ home: string,
36
+ slashTmpExists: boolean,
37
+ fallbackTmp: string,
38
+ ): string {
39
+ if (configured !== null) {
40
+ if (configured === "~") return home;
41
+ if (configured.startsWith("~/")) return join(home, configured.slice(2));
42
+ return configured;
43
+ }
44
+ return slashTmpExists ? "/tmp" : fallbackTmp;
45
+ }
46
+
13
47
  /** Quote a CSV field only when it needs it (comma, quote, or newline). */
14
48
  function csvField(value: string | number): string {
15
49
  const s = String(value);
package/index.ts CHANGED
@@ -13,7 +13,7 @@ import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-wor
13
13
  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
- import { collectUsageData, TAB_ORDER } from "./data";
16
+ import { collectUsageData, getAgentDir, TAB_ORDER } from "./data";
17
17
  import type { CollectProgress } from "./data";
18
18
  import type { BaseStats, ProviderStats, TabName, TotalStats, UsageData } from "./data";
19
19
  import {
@@ -26,8 +26,16 @@ import {
26
26
  TOTAL_SERIES_KEY,
27
27
  } from "./graph";
28
28
  import type { GraphGroupBy, GraphMetric, GraphModel } from "./graph";
29
- import { buildGraphCsv, buildInsightsJson, buildTableCsv, exportFileName } from "./export";
30
- import { writeFileSync } from "node:fs";
29
+ import {
30
+ buildGraphCsv,
31
+ buildInsightsJson,
32
+ buildTableCsv,
33
+ exportFileName,
34
+ parseExportDirSetting,
35
+ resolveExportDir,
36
+ } from "./export";
37
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
38
+ import { homedir, tmpdir } from "node:os";
31
39
  import { join } from "node:path";
32
40
 
33
41
  type ViewMode = "table" | "insights" | "graph";
@@ -524,9 +532,19 @@ class UsageComponent {
524
532
  content = buildTableCsv(visible.providers, visible.totals);
525
533
  }
526
534
  try {
527
- const path = join(process.cwd(), name);
535
+ let configured: string | null = null;
536
+ try {
537
+ configured = parseExportDirSetting(readFileSync(join(getAgentDir(), "settings.json"), "utf8"));
538
+ } catch {
539
+ // No settings file or unreadable: fall through to the default dir.
540
+ }
541
+ const home = homedir();
542
+ const dir = resolveExportDir(configured, home, existsSync("/tmp"), tmpdir());
543
+ mkdirSync(dir, { recursive: true });
544
+ const path = join(dir, name);
528
545
  writeFileSync(path, content);
529
- this.exportNote = { text: `Saved ${name}`, ok: true };
546
+ const shown = path.startsWith(home + "/") ? "~" + path.slice(home.length) : path;
547
+ this.exportNote = { text: `Saved ${shown}`, ok: true };
530
548
  } catch (err) {
531
549
  this.exportNote = { text: `Export failed: ${err instanceof Error ? err.message : String(err)}`, ok: false };
532
550
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-usage-extension",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Usage statistics dashboard for Pi sessions.",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Mustier",