@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
|
|
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
|
|
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`
|
|
59
|
+
`pi-openrouter` automatically tags OpenRouter requests with a `session_id` derived from the Pi session ID.
|
|
60
60
|
|
|
61
|
-
View the
|
|
61
|
+
View the OpenRouter session tag with:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
/session
|
|
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
|
-
//
|
|
47
|
-
const
|
|
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
|
|
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
|
|
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.
|
|
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://
|
|
30
|
+
"homepage": "https://pi-userland.dev",
|
|
31
31
|
"scripts": {
|
|
32
32
|
"lint": "eslint extensions/",
|
|
33
33
|
"format:check": "prettier --check extensions/",
|