@albinocrabs/o-switcher 0.3.0 → 0.3.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/dist/plugin.cjs CHANGED
@@ -239,27 +239,34 @@ var server = async (_input) => {
239
239
  state.registry.recordObservation(target.target_id, 0);
240
240
  state.circuitBreakers?.get(target.target_id)?.recordFailure();
241
241
  publishTuiState();
242
- state.logger?.info(
243
- { target_id: target.target_id, event_type: event.type },
244
- "Recorded failure from session event"
245
- );
246
- if (target.health_score < 0.3) {
247
- const unhealthyIds = matchingTargets.filter((t) => t.health_score < 0.3).map((t) => t.target_id);
242
+ const errorObj = error;
243
+ const statusCode = errorObj.statusCode ?? errorObj.error?.statusCode;
244
+ const errorName = String(errorObj.name ?? errorObj.error ?? "");
245
+ const isRateLimit = statusCode === 429 || statusCode === "429" || errorName.includes("RateLimitError") || errorName.includes("QuotaExceeded");
246
+ if (isRateLimit) {
247
+ state.logger?.info(
248
+ { target_id: target.target_id, statusCode },
249
+ "Rate limit hit \u2014 switching profile"
250
+ );
248
251
  chunkH72U2MNG_cjs.switchToNextProfile({
249
252
  provider: providerId,
250
253
  currentProfileId: target.target_id,
251
- excludeProfileIds: unhealthyIds,
252
254
  logger: state.logger
253
255
  }).then((result) => {
254
256
  if (result.success) {
255
257
  state.logger?.info(
256
258
  { from: result.from, to: result.to, provider: providerId },
257
- "Auto-switched to next profile after health drop"
259
+ "Switched to next profile after rate limit"
258
260
  );
259
261
  }
260
262
  }).catch((err) => {
261
- state.logger?.warn({ err }, "Failed to auto-switch profile");
263
+ state.logger?.warn({ err }, "Failed to switch profile");
262
264
  });
265
+ } else {
266
+ state.logger?.info(
267
+ { target_id: target.target_id, event_type: event.type },
268
+ "Recorded failure from session event"
269
+ );
263
270
  }
264
271
  }
265
272
  }
package/dist/plugin.js CHANGED
@@ -235,27 +235,34 @@ var server = async (_input) => {
235
235
  state.registry.recordObservation(target.target_id, 0);
236
236
  state.circuitBreakers?.get(target.target_id)?.recordFailure();
237
237
  publishTuiState();
238
- state.logger?.info(
239
- { target_id: target.target_id, event_type: event.type },
240
- "Recorded failure from session event"
241
- );
242
- if (target.health_score < 0.3) {
243
- const unhealthyIds = matchingTargets.filter((t) => t.health_score < 0.3).map((t) => t.target_id);
238
+ const errorObj = error;
239
+ const statusCode = errorObj.statusCode ?? errorObj.error?.statusCode;
240
+ const errorName = String(errorObj.name ?? errorObj.error ?? "");
241
+ const isRateLimit = statusCode === 429 || statusCode === "429" || errorName.includes("RateLimitError") || errorName.includes("QuotaExceeded");
242
+ if (isRateLimit) {
243
+ state.logger?.info(
244
+ { target_id: target.target_id, statusCode },
245
+ "Rate limit hit \u2014 switching profile"
246
+ );
244
247
  switchToNextProfile({
245
248
  provider: providerId,
246
249
  currentProfileId: target.target_id,
247
- excludeProfileIds: unhealthyIds,
248
250
  logger: state.logger
249
251
  }).then((result) => {
250
252
  if (result.success) {
251
253
  state.logger?.info(
252
254
  { from: result.from, to: result.to, provider: providerId },
253
- "Auto-switched to next profile after health drop"
255
+ "Switched to next profile after rate limit"
254
256
  );
255
257
  }
256
258
  }).catch((err) => {
257
- state.logger?.warn({ err }, "Failed to auto-switch profile");
259
+ state.logger?.warn({ err }, "Failed to switch profile");
258
260
  });
261
+ } else {
262
+ state.logger?.info(
263
+ { target_id: target.target_id, event_type: event.type },
264
+ "Recorded failure from session event"
265
+ );
259
266
  }
260
267
  }
261
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@albinocrabs/o-switcher",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Seamless OpenRouter profile rotation for OpenCode — buy multiple subscriptions, use as one pool",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/src/tui.tsx CHANGED
@@ -103,7 +103,7 @@ const SidebarFooter = () => {
103
103
  };
104
104
 
105
105
  /** Full status dashboard route. */
106
- const StatusDashboard = () => {
106
+ const StatusDashboard = (props: { api: TuiPluginApi }) => {
107
107
  const state = useTuiState();
108
108
 
109
109
  const content = (): string => {
@@ -167,11 +167,19 @@ const StatusDashboard = () => {
167
167
  const age = Date.now() - s.updated_at;
168
168
  const ageSec = Math.round(age / 1000);
169
169
  lines.push(` Updated ${ageSec}s ago`);
170
+ lines.push('');
171
+ lines.push(' Press Esc or q to go back');
170
172
 
171
173
  return lines.join('\n');
172
174
  };
173
175
 
174
- return <text wrapMode="none">{content()}</text>;
176
+ const onKeyDown = (e: { name: string }) => {
177
+ if (e.name === 'escape' || e.name === 'q') {
178
+ props.api.route.navigate('home');
179
+ }
180
+ };
181
+
182
+ return <box focused onKeyDown={onKeyDown}><text wrapMode="none">{content()}</text></box>;
175
183
  };
176
184
 
177
185
  // ── Plugin entry point ────────────────────────────────────────────
@@ -190,7 +198,7 @@ const tui = async (api: TuiPluginApi) => {
190
198
  api.route.register([
191
199
  {
192
200
  name: 'o-switcher-status',
193
- render: () => <StatusDashboard />,
201
+ render: () => <StatusDashboard api={api} />,
194
202
  },
195
203
  ]);
196
204