@robhowley/pi-openrouter 0.6.0 → 0.7.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/README.md CHANGED
@@ -21,7 +21,7 @@ export OPENROUTER_MANAGEMENT_KEY=sk-or-...
21
21
 
22
22
  ## Usage
23
23
 
24
- Type `/openrouter-usage` in Pi to open the usage overlay.
24
+ Type `/openrouter usage` in Pi to open the usage overlay.
25
25
 
26
26
  The overlay shows:
27
27
  - **Month spend** vs cap with percentage
@@ -37,7 +37,7 @@ The extension refreshes data in the background every 30 seconds (with exponentia
37
37
 
38
38
  ## Account health
39
39
 
40
- Type `/openrouter-account` in Pi to open the account health overlay.
40
+ Type `/openrouter account` in Pi to open the account health overlay.
41
41
 
42
42
  The overlay shows:
43
43
 
@@ -56,25 +56,17 @@ Select a key from the list to inspect its limit, usage, reset cadence, and BYOK
56
56
 
57
57
  ## Session tracking
58
58
 
59
- `pi-openrouter` automatically tags OpenRouter requests with `session_id` field set to the Pi session's ID.
59
+ `pi-openrouter` automatically tags OpenRouter requests with a `session_id` derived from the Pi session ID.
60
60
 
61
- View the Pi session ID with
61
+ View the OpenRouter session tag with:
62
62
 
63
63
  ```bash
64
- /session # [uuid]
65
- ```
66
-
67
- The session can be tracked in OpenRouter's logs under the following ID:
68
-
69
- ```bash
70
- /openrouter-session
64
+ /openrouter session
71
65
 
72
66
  # OpenRouter session_id
73
67
  pi:[uuid]
74
68
  ```
75
69
 
76
- This enables session-level tracking in the OpenRouter Logs → Sessions page.
77
-
78
70
  ## License
79
71
 
80
72
  MIT
@@ -43,8 +43,10 @@ describe('aggregateUsage', () => {
43
43
  totalCredits: 100,
44
44
  };
45
45
  // Use a date that's within the last 7 days of when the test runs.
46
- // We use a date from the past month that's been long enough to survive timezone offsets.
47
- const date = '2026-05-01';
46
+ // Get today's date in UTC and subtract a few days to ensure it's in the week window.
47
+ const now = new Date();
48
+ const testDate = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
49
+ const date = `${testDate.getUTCFullYear()}-${String(testDate.getUTCMonth() + 1).padStart(2, '0')}-${String(testDate.getUTCDate()).padStart(2, '0')}`;
48
50
  const analytics: ActivityItem[] = [
49
51
  {
50
52
  date: date,
@@ -77,9 +79,9 @@ describe('aggregateUsage', () => {
77
79
  const result = aggregateUsage(credits, analytics);
78
80
 
79
81
  expect(result.month).toBe(38.42);
80
- // Data from May 1 should be in the week (but may not be in "today" depending on timezone)
82
+ // Data from the test date should be in the week (7 days ago window)
81
83
  expect(result.week).toBeGreaterThan(0);
82
- // Today's data might not be from May 1 due to timezone, so just check month
84
+ // Today's data might not be from the test date due to timezone, so just check month
83
85
  });
84
86
 
85
87
  it('should calculate burn rate correctly', () => {
@@ -173,15 +173,21 @@ export class AccountOverlayComponent {
173
173
  return Math.max(MIN_WIDTH, this.keyInfo && this.keyInfo.length > 0 ? 55 : 50);
174
174
  }
175
175
 
176
+ /** Get the header row for the account overlay */
177
+ private getAccountHeaderRow(): string {
178
+ return row(
179
+ this.theme.fg('accent', this.theme.bold(' ◈ OpenRouter Account · /openrouter account')),
180
+ this.width,
181
+ );
182
+ }
183
+
176
184
  private buildLines(): string[] {
177
185
  const th = this.theme;
178
186
  const lines: string[] = [];
179
187
 
180
188
  if (this.error) {
181
189
  lines.push(boxTop(this.width));
182
- lines.push(
183
- row(th.fg('accent', th.bold(' ◈ OpenRouter Account · /openrouter-account')), this.width),
184
- );
190
+ lines.push(this.getAccountHeaderRow());
185
191
  lines.push(emptyRow(this.width));
186
192
  lines.push(row(th.fg('error', this.error), this.width));
187
193
  lines.push(boxBottom(this.width));
@@ -190,9 +196,7 @@ export class AccountOverlayComponent {
190
196
  }
191
197
 
192
198
  lines.push(boxTop(this.width));
193
- lines.push(
194
- row(th.fg('accent', th.bold(' ◈ OpenRouter Account · /openrouter-account')), this.width),
195
- );
199
+ lines.push(this.getAccountHeaderRow());
196
200
  lines.push(emptyRow(this.width));
197
201
 
198
202
  // Total spend line (sum of all key spends)
@@ -180,6 +180,46 @@ export default function (pi: ExtensionAPI) {
180
180
  await showAccountOverlay(ctx);
181
181
  },
182
182
  });
183
+
184
+ // Single entry point with subcommands: /openrouter [usage|account|session]
185
+ pi.registerCommand('openrouter', {
186
+ description: 'OpenRouter commands: usage, account, session',
187
+ getArgumentCompletions: (prefix: string) => {
188
+ const subcommands = ['usage', 'account', 'session'];
189
+ const items = subcommands
190
+ .filter((s) => s.startsWith(prefix))
191
+ .map((s) => ({ value: s, label: s }));
192
+ return items.length > 0 ? items : null;
193
+ },
194
+ handler: async (args, ctx) => {
195
+ const subcommand = args.trim().split(/\s+/)[0] || '';
196
+
197
+ switch (subcommand) {
198
+ case 'usage': {
199
+ startBackgroundRefresh();
200
+ await showUsageOverlay(ctx, undefined);
201
+ break;
202
+ }
203
+ case 'account': {
204
+ await showAccountOverlay(ctx);
205
+ break;
206
+ }
207
+ case 'session': {
208
+ ctx.ui.notify(`OpenRouter session_id\n${getCurrentSessionId(ctx)}`, 'info');
209
+ break;
210
+ }
211
+ default: {
212
+ const available = ['usage', 'account', 'session'];
213
+ const message =
214
+ available.length > 0
215
+ ? `Available subcommands: ${available.join(', ')}${available.length > 1 ? '' : ''}`
216
+ : 'No subcommands available';
217
+ ctx.ui.notify(`OpenRouter subcommands\n${message}`, 'error');
218
+ break;
219
+ }
220
+ }
221
+ },
222
+ });
183
223
  }
184
224
 
185
225
  async function showAccountOverlay(ctx: ExtensionContext) {
@@ -118,6 +118,14 @@ export class UsageOverlayComponent {
118
118
  return Math.max(MIN_WIDTH, innerWidth + 4) + 6; // +4 for borders, +6 for visual padding (3 on each side)
119
119
  }
120
120
 
121
+ /** Get the header row for the usage overlay */
122
+ private getUsageHeaderRow(): string {
123
+ return row(
124
+ this.theme.fg('accent', this.theme.bold(' ◈ OpenRouter Usage · /openrouter usage')),
125
+ this.width,
126
+ );
127
+ }
128
+
121
129
  private buildLines(
122
130
  summary: UsageSummary | null,
123
131
  error: string | null,
@@ -128,9 +136,7 @@ export class UsageOverlayComponent {
128
136
 
129
137
  if (error) {
130
138
  lines.push(boxTop(this.width));
131
- lines.push(
132
- row(th.fg('accent', th.bold(' ◈ OpenRouter Usage · /openrouter-usage')), this.width),
133
- );
139
+ lines.push(this.getUsageHeaderRow());
134
140
  lines.push(emptyRow(this.width));
135
141
  lines.push(row(th.fg('error', error), this.width));
136
142
  if (cachedMinutesAgo !== null) {
@@ -145,9 +151,7 @@ export class UsageOverlayComponent {
145
151
 
146
152
  if (!summary) {
147
153
  lines.push(boxTop(this.width));
148
- lines.push(
149
- row(th.fg('accent', th.bold(' ◈ OpenRouter Usage · /openrouter-usage')), this.width),
150
- );
154
+ lines.push(this.getUsageHeaderRow());
151
155
  lines.push(emptyRow(this.width));
152
156
  lines.push(row(th.fg('dim', 'No usage data available.'), this.width));
153
157
  lines.push(boxBottom(this.width));
@@ -157,9 +161,7 @@ export class UsageOverlayComponent {
157
161
 
158
162
  // Summary view (subcommand views TODO)
159
163
  lines.push(boxTop(this.width));
160
- lines.push(
161
- row(th.fg('accent', th.bold(' ◈ OpenRouter Usage · /openrouter-usage')), this.width),
162
- );
164
+ lines.push(this.getUsageHeaderRow());
163
165
  lines.push(emptyRow(this.width));
164
166
 
165
167
  // Month row: amount stays with label, cap percentage right-aligned
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robhowley/pi-openrouter",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Live OpenRouter TUI overlays for spend, credits, key limits, burn rate, model usage, and session tagging.",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "url": "https://github.com/robhowley/pi-userland.git",
28
28
  "directory": "packages/pi-openrouter"
29
29
  },
30
- "homepage": "https://github.com/robhowley/pi-userland/tree/main/packages/pi-openrouter",
30
+ "homepage": "https://pi-userland.dev",
31
31
  "scripts": {
32
32
  "lint": "eslint extensions/",
33
33
  "format:check": "prettier --check extensions/",