@albinocrabs/o-switcher 0.1.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 57f1eb9: Add TUI sidebar panel and status dashboard for OpenCode
8
+ - Sidebar footer shows active profile, health %, and target count
9
+ - `/switcher` slash command opens full status dashboard with all targets
10
+ - File-based state bridge between server plugin and TUI (atomic writes, debounced)
11
+
3
12
  All notable changes to this project will be documented in this file.
4
13
 
5
14
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
@@ -4,10 +4,10 @@ import crypto from 'crypto';
4
4
  import { EventEmitter } from 'eventemitter3';
5
5
  import { CircuitState, ConsecutiveBreaker, CountBreaker, BrokenCircuitError, IsolatedCircuitError, circuitBreaker, handleAll } from 'cockatiel';
6
6
  import PQueue from 'p-queue';
7
+ import { tool } from '@opencode-ai/plugin/tool';
7
8
  import { readFile, mkdir, writeFile, rename, watch } from 'fs/promises';
8
9
  import { homedir } from 'os';
9
10
  import { join, dirname } from 'path';
10
- import { tool } from '@opencode-ai/plugin/tool';
11
11
 
12
12
  // src/config/defaults.ts
13
13
  var DEFAULT_RETRY_BUDGET = 3;
@@ -1355,6 +1355,78 @@ var reloadConfig = (deps, rawConfig) => {
1355
1355
  throw err;
1356
1356
  }
1357
1357
  };
1358
+ var { schema: z3 } = tool;
1359
+ var createOperatorTools = (deps) => ({
1360
+ listTargets: tool({
1361
+ description: "List all routing targets with health scores, states, and circuit breaker status.",
1362
+ args: {},
1363
+ async execute() {
1364
+ deps.logger.info({ op: "listTargets" }, "operator: listTargets");
1365
+ const result = listTargets(deps);
1366
+ return JSON.stringify(result, null, 2);
1367
+ }
1368
+ }),
1369
+ pauseTarget: tool({
1370
+ description: "Pause a target, preventing new requests from being routed to it.",
1371
+ args: { target_id: z3.string().min(1) },
1372
+ async execute(args) {
1373
+ deps.logger.info({ op: "pauseTarget", target_id: args.target_id }, "operator: pauseTarget");
1374
+ const result = pauseTarget(deps, args.target_id);
1375
+ return JSON.stringify(result, null, 2);
1376
+ }
1377
+ }),
1378
+ resumeTarget: tool({
1379
+ description: "Resume a previously paused or disabled target, allowing new requests.",
1380
+ args: { target_id: z3.string().min(1) },
1381
+ async execute(args) {
1382
+ deps.logger.info({ op: "resumeTarget", target_id: args.target_id }, "operator: resumeTarget");
1383
+ const result = resumeTarget(deps, args.target_id);
1384
+ return JSON.stringify(result, null, 2);
1385
+ }
1386
+ }),
1387
+ drainTarget: tool({
1388
+ description: "Drain a target, allowing in-flight requests to complete but preventing new ones.",
1389
+ args: { target_id: z3.string().min(1) },
1390
+ async execute(args) {
1391
+ deps.logger.info({ op: "drainTarget", target_id: args.target_id }, "operator: drainTarget");
1392
+ const result = drainTarget(deps, args.target_id);
1393
+ return JSON.stringify(result, null, 2);
1394
+ }
1395
+ }),
1396
+ disableTarget: tool({
1397
+ description: "Disable a target entirely, removing it from routing.",
1398
+ args: { target_id: z3.string().min(1) },
1399
+ async execute(args) {
1400
+ deps.logger.info(
1401
+ { op: "disableTarget", target_id: args.target_id },
1402
+ "operator: disableTarget"
1403
+ );
1404
+ const result = disableTarget(deps, args.target_id);
1405
+ return JSON.stringify(result, null, 2);
1406
+ }
1407
+ }),
1408
+ inspectRequest: tool({
1409
+ description: "Inspect a request trace by ID, showing attempts, segments, and outcome.",
1410
+ args: { request_id: z3.string().min(1) },
1411
+ async execute(args) {
1412
+ deps.logger.info(
1413
+ { op: "inspectRequest", request_id: args.request_id },
1414
+ "operator: inspectRequest"
1415
+ );
1416
+ const result = inspectRequest(deps, args.request_id);
1417
+ return JSON.stringify(result, null, 2);
1418
+ }
1419
+ }),
1420
+ reloadConfig: tool({
1421
+ description: "Reload routing configuration with diff-apply. Validates new config before applying.",
1422
+ args: { config: z3.record(z3.string(), z3.unknown()) },
1423
+ async execute(args) {
1424
+ deps.logger.info({ op: "reloadConfig" }, "operator: reloadConfig");
1425
+ const result = reloadConfig(deps, args.config);
1426
+ return JSON.stringify(result, null, 2);
1427
+ }
1428
+ })
1429
+ });
1358
1430
  var PROFILES_DIR = join(homedir(), ".local", "share", "o-switcher");
1359
1431
  var PROFILES_PATH = join(PROFILES_DIR, "profiles.json");
1360
1432
  var loadProfiles = async (path = PROFILES_PATH) => {
@@ -1551,7 +1623,7 @@ var createAuthWatcher = (options) => {
1551
1623
  };
1552
1624
  return { start, stop };
1553
1625
  };
1554
- var { schema: z3 } = tool;
1626
+ var { schema: z4 } = tool;
1555
1627
  var createProfileTools = (options) => ({
1556
1628
  profilesList: tool({
1557
1629
  description: "List all saved auth profiles with provider, type, and creation date.",
@@ -1570,7 +1642,7 @@ var createProfileTools = (options) => ({
1570
1642
  }),
1571
1643
  profilesRemove: tool({
1572
1644
  description: "Remove a saved auth profile by ID.",
1573
- args: { id: z3.string().min(1) },
1645
+ args: { id: z4.string().min(1) },
1574
1646
  async execute(args) {
1575
1647
  const store = await loadProfiles(options?.profilesPath);
1576
1648
  const { store: newStore, removed } = removeProfile(store, args.id);
@@ -1587,4 +1659,4 @@ var createProfileTools = (options) => ({
1587
1659
  })
1588
1660
  });
1589
1661
 
1590
- export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateConfig };
1662
+ export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateConfig };
@@ -6,10 +6,10 @@ var crypto = require('crypto');
6
6
  var eventemitter3 = require('eventemitter3');
7
7
  var cockatiel = require('cockatiel');
8
8
  var PQueue = require('p-queue');
9
+ var tool = require('@opencode-ai/plugin/tool');
9
10
  var promises = require('fs/promises');
10
11
  var os = require('os');
11
12
  var path = require('path');
12
- var tool = require('@opencode-ai/plugin/tool');
13
13
 
14
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
15
 
@@ -1363,6 +1363,78 @@ var reloadConfig = (deps, rawConfig) => {
1363
1363
  throw err;
1364
1364
  }
1365
1365
  };
1366
+ var { schema: z3 } = tool.tool;
1367
+ var createOperatorTools = (deps) => ({
1368
+ listTargets: tool.tool({
1369
+ description: "List all routing targets with health scores, states, and circuit breaker status.",
1370
+ args: {},
1371
+ async execute() {
1372
+ deps.logger.info({ op: "listTargets" }, "operator: listTargets");
1373
+ const result = listTargets(deps);
1374
+ return JSON.stringify(result, null, 2);
1375
+ }
1376
+ }),
1377
+ pauseTarget: tool.tool({
1378
+ description: "Pause a target, preventing new requests from being routed to it.",
1379
+ args: { target_id: z3.string().min(1) },
1380
+ async execute(args) {
1381
+ deps.logger.info({ op: "pauseTarget", target_id: args.target_id }, "operator: pauseTarget");
1382
+ const result = pauseTarget(deps, args.target_id);
1383
+ return JSON.stringify(result, null, 2);
1384
+ }
1385
+ }),
1386
+ resumeTarget: tool.tool({
1387
+ description: "Resume a previously paused or disabled target, allowing new requests.",
1388
+ args: { target_id: z3.string().min(1) },
1389
+ async execute(args) {
1390
+ deps.logger.info({ op: "resumeTarget", target_id: args.target_id }, "operator: resumeTarget");
1391
+ const result = resumeTarget(deps, args.target_id);
1392
+ return JSON.stringify(result, null, 2);
1393
+ }
1394
+ }),
1395
+ drainTarget: tool.tool({
1396
+ description: "Drain a target, allowing in-flight requests to complete but preventing new ones.",
1397
+ args: { target_id: z3.string().min(1) },
1398
+ async execute(args) {
1399
+ deps.logger.info({ op: "drainTarget", target_id: args.target_id }, "operator: drainTarget");
1400
+ const result = drainTarget(deps, args.target_id);
1401
+ return JSON.stringify(result, null, 2);
1402
+ }
1403
+ }),
1404
+ disableTarget: tool.tool({
1405
+ description: "Disable a target entirely, removing it from routing.",
1406
+ args: { target_id: z3.string().min(1) },
1407
+ async execute(args) {
1408
+ deps.logger.info(
1409
+ { op: "disableTarget", target_id: args.target_id },
1410
+ "operator: disableTarget"
1411
+ );
1412
+ const result = disableTarget(deps, args.target_id);
1413
+ return JSON.stringify(result, null, 2);
1414
+ }
1415
+ }),
1416
+ inspectRequest: tool.tool({
1417
+ description: "Inspect a request trace by ID, showing attempts, segments, and outcome.",
1418
+ args: { request_id: z3.string().min(1) },
1419
+ async execute(args) {
1420
+ deps.logger.info(
1421
+ { op: "inspectRequest", request_id: args.request_id },
1422
+ "operator: inspectRequest"
1423
+ );
1424
+ const result = inspectRequest(deps, args.request_id);
1425
+ return JSON.stringify(result, null, 2);
1426
+ }
1427
+ }),
1428
+ reloadConfig: tool.tool({
1429
+ description: "Reload routing configuration with diff-apply. Validates new config before applying.",
1430
+ args: { config: z3.record(z3.string(), z3.unknown()) },
1431
+ async execute(args) {
1432
+ deps.logger.info({ op: "reloadConfig" }, "operator: reloadConfig");
1433
+ const result = reloadConfig(deps, args.config);
1434
+ return JSON.stringify(result, null, 2);
1435
+ }
1436
+ })
1437
+ });
1366
1438
  var PROFILES_DIR = path.join(os.homedir(), ".local", "share", "o-switcher");
1367
1439
  var PROFILES_PATH = path.join(PROFILES_DIR, "profiles.json");
1368
1440
  var loadProfiles = async (path = PROFILES_PATH) => {
@@ -1559,7 +1631,7 @@ var createAuthWatcher = (options) => {
1559
1631
  };
1560
1632
  return { start, stop };
1561
1633
  };
1562
- var { schema: z3 } = tool.tool;
1634
+ var { schema: z4 } = tool.tool;
1563
1635
  var createProfileTools = (options) => ({
1564
1636
  profilesList: tool.tool({
1565
1637
  description: "List all saved auth profiles with provider, type, and creation date.",
@@ -1578,7 +1650,7 @@ var createProfileTools = (options) => ({
1578
1650
  }),
1579
1651
  profilesRemove: tool.tool({
1580
1652
  description: "Remove a saved auth profile by ID.",
1581
- args: { id: z3.string().min(1) },
1653
+ args: { id: z4.string().min(1) },
1582
1654
  async execute(args) {
1583
1655
  const store = await loadProfiles(options?.profilesPath);
1584
1656
  const { store: newStore, removed } = removeProfile(store, args.id);
@@ -1632,6 +1704,7 @@ exports.createConcurrencyTracker = createConcurrencyTracker;
1632
1704
  exports.createCooldownManager = createCooldownManager;
1633
1705
  exports.createFailoverOrchestrator = createFailoverOrchestrator;
1634
1706
  exports.createLogSubscriber = createLogSubscriber;
1707
+ exports.createOperatorTools = createOperatorTools;
1635
1708
  exports.createProfileTools = createProfileTools;
1636
1709
  exports.createRegistry = createRegistry;
1637
1710
  exports.createRequestLogger = createRequestLogger;