@s-berdia/opencode-router 1.0.0 → 1.1.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 +12 -0
- package/USAGE.md +75 -6
- package/dist/bin.js +161 -12
- package/dist/bin.js.map +1 -1
- package/dist/index.js +92 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,10 @@ the router, read its [known limits](./USAGE.md#11-known-limits).
|
|
|
66
66
|
|
|
67
67
|
The router now selects models automatically. You do not have to do more.
|
|
68
68
|
|
|
69
|
+
OpenCode does not update plugins automatically. When a new version of the router is
|
|
70
|
+
available, the router shows a note. Then type `/routing-update` and restart OpenCode.
|
|
71
|
+
See [Update the router](./USAGE.md#12-update-the-router).
|
|
72
|
+
|
|
69
73
|
## Commands
|
|
70
74
|
|
|
71
75
|
### In OpenCode
|
|
@@ -76,6 +80,7 @@ The router now selects models automatically. You do not have to do more.
|
|
|
76
80
|
| `/routing-status` | Show the model in use for each agent (after failover) and its 5-hour quota use |
|
|
77
81
|
| `/routing-plan go-plus` | Save a plan profile (`go`, `go-plus`, or `free`) and apply it immediately |
|
|
78
82
|
| `/routing-eval` | Send one test prompt to the `build` models and write a report |
|
|
83
|
+
| `/routing-update` | Look for a newer version and remove the old copy that OpenCode keeps. Then restart OpenCode |
|
|
79
84
|
|
|
80
85
|
### In a terminal
|
|
81
86
|
|
|
@@ -88,6 +93,7 @@ npx @s-berdia/opencode-router status # models in use and quota u
|
|
|
88
93
|
npx @s-berdia/opencode-router plan go-plus # save the plan profile and rebuild
|
|
89
94
|
npx @s-berdia/opencode-router cat # catalog file, date, model count
|
|
90
95
|
npx @s-berdia/opencode-router install --scope global # write slash-command files
|
|
96
|
+
npx @s-berdia/opencode-router@latest update # remove OpenCode's old copy
|
|
91
97
|
npx @s-berdia/opencode-router --help # all commands and options
|
|
92
98
|
```
|
|
93
99
|
|
|
@@ -140,6 +146,7 @@ or a value that is not valid, the router ignores it and shows a note in the scor
|
|
|
140
146
|
| `blend` | `{ "input": 0.75, "output": 0.25, "cacheRead": 0.1 }` | Token mix used to calculate the cost of one million tokens |
|
|
141
147
|
| `enduranceScale` | `{ "minTokensPer5h": 0.25, "maxTokensPer5h": 40 }` | Millions of tokens per 5 hours that give an endurance score of 0 and 100 |
|
|
142
148
|
| `feeds` | `{ "enabled": false }` | Optional benchmark feed: `{ "enabled": true, "url": "https://...", "timeoutMs": 5000 }` |
|
|
149
|
+
| `updateCheck` | `true` | Once each 24 hours, ask npm for the newest router version. `false` stops the check |
|
|
143
150
|
|
|
144
151
|
The [usage guide](./USAGE.md#6-change-the-router-settings) has examples for each key.
|
|
145
152
|
|
|
@@ -172,6 +179,7 @@ score = w_intelligence * intelligence
|
|
|
172
179
|
| `~/.config/opencode/router/routing-table.json` | The last routing table |
|
|
173
180
|
| `~/.config/opencode/router/last-apply.json` | The result of the last live apply |
|
|
174
181
|
| `~/.config/opencode/router/eval-report.md` | The report of the last `/routing-eval` |
|
|
182
|
+
| `~/.config/opencode/router/update-check.json` | The time of the last update check and the newest version on npm |
|
|
175
183
|
| `opencode.json` or `opencode.jsonc` (global, or the project file with `--scope project`) | `model`, `small_model`, and `agent.<role>.model` and `variant`. Comments and all other keys stay. A file with a JSON error is not changed |
|
|
176
184
|
|
|
177
185
|
If `XDG_CONFIG_HOME` is set, the router uses `$XDG_CONFIG_HOME/opencode` in place of
|
|
@@ -183,6 +191,10 @@ Models with `-free` in their name can collect your prompts for training. The sco
|
|
|
183
191
|
marks them `free*`. To keep your main work off these models, set
|
|
184
192
|
`"privacy": { "freeModelsOk": false }`. To never use them, set `"denyModels": ["*-free"]`.
|
|
185
193
|
|
|
194
|
+
Once each 24 hours, the router sends one request to `registry.npmjs.org` to get the
|
|
195
|
+
newest version number of the router. It sends no other data. To stop this, set
|
|
196
|
+
`"updateCheck": false`.
|
|
197
|
+
|
|
186
198
|
## Development
|
|
187
199
|
|
|
188
200
|
```bash
|
package/USAGE.md
CHANGED
|
@@ -16,7 +16,8 @@ Do the steps in sequence. Each command has a label that tells you which shell to
|
|
|
16
16
|
9. [Files and folders](#9-files-and-folders)
|
|
17
17
|
10. [Troubleshooting](#10-troubleshooting)
|
|
18
18
|
11. [Known limits](#11-known-limits)
|
|
19
|
-
12. [
|
|
19
|
+
12. [Update the router](#12-update-the-router)
|
|
20
|
+
13. [Remove the router](#13-remove-the-router)
|
|
20
21
|
|
|
21
22
|
## 1. Before you start
|
|
22
23
|
|
|
@@ -119,8 +120,8 @@ Use this option to test changes to the code.
|
|
|
119
120
|
|
|
120
121
|
1. In the OpenCode prompt, type `/` and look at the list of commands. Make sure that
|
|
121
122
|
these commands are in the list: `/refresh-routing`, `/routing-status`,
|
|
122
|
-
`/routing-plan`, and `/routing-
|
|
123
|
-
[Troubleshooting](#10-troubleshooting).
|
|
123
|
+
`/routing-plan`, `/routing-eval`, and `/routing-update`. If they are not in the list,
|
|
124
|
+
go to [Troubleshooting](#10-troubleshooting).
|
|
124
125
|
2. Type this command and push Enter:
|
|
125
126
|
|
|
126
127
|
```text
|
|
@@ -311,6 +312,19 @@ models of that family.
|
|
|
311
312
|
}
|
|
312
313
|
```
|
|
313
314
|
|
|
315
|
+
### Stop the update check
|
|
316
|
+
|
|
317
|
+
Once each 24 hours, the router asks npm for the newest version number of the router. It
|
|
318
|
+
sends one request to `registry.npmjs.org` and no other data. When a newer version is
|
|
319
|
+
available, the router shows a toast in OpenCode and a line under the status and the
|
|
320
|
+
scorecard. To stop the check:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"updateCheck": false
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
314
328
|
### Advanced cost settings
|
|
315
329
|
|
|
316
330
|
| Key | Default | Meaning |
|
|
@@ -356,6 +370,7 @@ These commands work in PowerShell, CMD, Git Bash, macOS, and Linux.
|
|
|
356
370
|
| `opencode-router plan go-plus --scope global` | Save the plan in the global settings, then calculate and write the routing table |
|
|
357
371
|
| `opencode-router cat` | Show the catalog path, its date, and the number of models |
|
|
358
372
|
| `opencode-router install --scope global` | Write the slash commands as files in `~/.config/opencode/command/`. If `router.config.json` does not exist, it also writes an empty one |
|
|
373
|
+
| `opencode-router update` | Ask npm for the newest version. If the copy that OpenCode keeps is older, remove it. See [Update the router](#12-update-the-router) |
|
|
359
374
|
|
|
360
375
|
The terminal tool reads the global `router.config.json` and the project file of the
|
|
361
376
|
current folder, in the same way as the plugin.
|
|
@@ -436,6 +451,7 @@ The router writes these files:
|
|
|
436
451
|
| `~/.config/opencode/router/routing-table.json` | Plugin | The last routing table |
|
|
437
452
|
| `~/.config/opencode/router/last-apply.json` | Plugin | The result of the last live apply |
|
|
438
453
|
| `~/.config/opencode/router/eval-report.md` | `/routing-eval` | The test report |
|
|
454
|
+
| `~/.config/opencode/router/update-check.json` | Plugin, `status`, `refresh` | The time of the last update check and the newest version on npm |
|
|
439
455
|
| `~/.config/opencode/opencode.json` | `/refresh-routing`, `/routing-plan` | `model`, `small_model`, and `agent.<role>.model` and `variant` |
|
|
440
456
|
| `~/.config/opencode/router.config.json` | `/routing-plan`, `install` (only if missing) | Your router settings |
|
|
441
457
|
|
|
@@ -466,7 +482,7 @@ Do not commit the `router/` folder. It contains your usage data.
|
|
|
466
482
|
|
|
467
483
|
## 11. Known limits
|
|
468
484
|
|
|
469
|
-
These are the limits of version 1.
|
|
485
|
+
These are the limits of version 1.1.0.
|
|
470
486
|
|
|
471
487
|
1. The router sets the model of the `plan`, `build`, `general`, and `explore` agents at
|
|
472
488
|
each message. If you select a different model by hand, the router replaces it. To
|
|
@@ -483,7 +499,59 @@ These are the limits of version 1.0.0.
|
|
|
483
499
|
rate-limit error for a full session puts only the model of that session into a
|
|
484
500
|
cooldown.
|
|
485
501
|
|
|
486
|
-
## 12.
|
|
502
|
+
## 12. Update the router
|
|
503
|
+
|
|
504
|
+
OpenCode downloads the plugin one time and then uses its own copy. It does not download
|
|
505
|
+
a newer version automatically. The router looks for a newer version once each 24 hours
|
|
506
|
+
and tells you when one is available.
|
|
507
|
+
|
|
508
|
+
### With the slash command
|
|
509
|
+
|
|
510
|
+
1. In the OpenCode prompt, type this command and push Enter:
|
|
511
|
+
|
|
512
|
+
```text
|
|
513
|
+
/routing-update
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
2. Read the result:
|
|
517
|
+
- `OpenCode has opencode-router <version>, the newest version.`: you do not have to
|
|
518
|
+
do more.
|
|
519
|
+
- `Removed the cached copy: ...`: go to step 3.
|
|
520
|
+
- `opencode.json pins an older version ...`: do the steps in
|
|
521
|
+
[With a fixed version](#with-a-fixed-version).
|
|
522
|
+
3. Close OpenCode. Start OpenCode again. OpenCode downloads the newest version when it
|
|
523
|
+
starts.
|
|
524
|
+
4. Type `/routing-status`. If the line `opencode-router <version> is available` does not
|
|
525
|
+
show, the update is complete.
|
|
526
|
+
|
|
527
|
+
Between steps 2 and 3, the router slash commands do not work, because the files that they
|
|
528
|
+
use are removed. Restart OpenCode immediately.
|
|
529
|
+
|
|
530
|
+
### From a terminal
|
|
531
|
+
|
|
532
|
+
If the slash commands do not work, close OpenCode and run this command. Then start
|
|
533
|
+
OpenCode again.
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
npx @s-berdia/opencode-router@latest update
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
This command works in PowerShell, CMD, Git Bash, macOS, and Linux.
|
|
540
|
+
|
|
541
|
+
### With a fixed version
|
|
542
|
+
|
|
543
|
+
To select the version yourself, write it in the `plugin` list:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"plugin": ["@s-berdia/opencode-router@1.1.0"]
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
To update, change the number to the new version and restart OpenCode. The newest version
|
|
552
|
+
is on <https://www.npmjs.com/package/@s-berdia/opencode-router>.
|
|
553
|
+
|
|
554
|
+
## 13. Remove the router
|
|
487
555
|
|
|
488
556
|
1. Open `opencode.json` (see [step 2](#2-install-the-plugin)).
|
|
489
557
|
2. Remove `"@s-berdia/opencode-router"` from the `plugin` list.
|
|
@@ -509,5 +577,6 @@ These are the limits of version 1.0.0.
|
|
|
509
577
|
```
|
|
510
578
|
|
|
511
579
|
6. If you ran `install`, delete these files from `~/.config/opencode/command/`:
|
|
512
|
-
`refresh-routing.md`, `routing-status.md`, `routing-plan.md`,
|
|
580
|
+
`refresh-routing.md`, `routing-status.md`, `routing-plan.md`, `routing-eval.md`, and
|
|
581
|
+
`routing-update.md`.
|
|
513
582
|
7. Restart OpenCode.
|
package/dist/bin.js
CHANGED
|
@@ -822,12 +822,12 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
822
822
|
}
|
|
823
823
|
return result;
|
|
824
824
|
}
|
|
825
|
-
function findNodeAtLocation(root,
|
|
825
|
+
function findNodeAtLocation(root, path6) {
|
|
826
826
|
if (!root) {
|
|
827
827
|
return void 0;
|
|
828
828
|
}
|
|
829
829
|
let node = root;
|
|
830
|
-
for (let segment of
|
|
830
|
+
for (let segment of path6) {
|
|
831
831
|
if (typeof segment === "string") {
|
|
832
832
|
if (node.type !== "object" || !Array.isArray(node.children)) {
|
|
833
833
|
return void 0;
|
|
@@ -1181,14 +1181,14 @@ function getNodeType(value) {
|
|
|
1181
1181
|
|
|
1182
1182
|
// node_modules/jsonc-parser/lib/esm/impl/edit.js
|
|
1183
1183
|
function setProperty(text, originalPath, value, options) {
|
|
1184
|
-
const
|
|
1184
|
+
const path6 = originalPath.slice();
|
|
1185
1185
|
const errors = [];
|
|
1186
1186
|
const root = parseTree(text, errors);
|
|
1187
1187
|
let parent = void 0;
|
|
1188
1188
|
let lastSegment = void 0;
|
|
1189
|
-
while (
|
|
1190
|
-
lastSegment =
|
|
1191
|
-
parent = findNodeAtLocation(root,
|
|
1189
|
+
while (path6.length > 0) {
|
|
1190
|
+
lastSegment = path6.pop();
|
|
1191
|
+
parent = findNodeAtLocation(root, path6);
|
|
1192
1192
|
if (parent === void 0 && value !== void 0) {
|
|
1193
1193
|
if (typeof lastSegment === "string") {
|
|
1194
1194
|
value = { [lastSegment]: value };
|
|
@@ -1413,8 +1413,8 @@ function printParseErrorCode(code) {
|
|
|
1413
1413
|
}
|
|
1414
1414
|
return "<unknown ParseErrorCode>";
|
|
1415
1415
|
}
|
|
1416
|
-
function modify(text,
|
|
1417
|
-
return setProperty(text,
|
|
1416
|
+
function modify(text, path6, value, options) {
|
|
1417
|
+
return setProperty(text, path6, value, options);
|
|
1418
1418
|
}
|
|
1419
1419
|
function applyEdits(text, edits) {
|
|
1420
1420
|
let sortedEdits = edits.slice(0).sort((a, b) => {
|
|
@@ -1443,10 +1443,13 @@ function globalConfigDir() {
|
|
|
1443
1443
|
const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".config");
|
|
1444
1444
|
return path.join(base, "opencode");
|
|
1445
1445
|
}
|
|
1446
|
-
function
|
|
1446
|
+
function opencodeCacheDir() {
|
|
1447
1447
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
1448
1448
|
const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".cache");
|
|
1449
|
-
return path.join(base, "opencode"
|
|
1449
|
+
return path.join(base, "opencode");
|
|
1450
|
+
}
|
|
1451
|
+
function catalogCacheFile() {
|
|
1452
|
+
return path.join(opencodeCacheDir(), "models.json");
|
|
1450
1453
|
}
|
|
1451
1454
|
function globalRouterConfigFile() {
|
|
1452
1455
|
return path.join(globalConfigDir(), "router.config.json");
|
|
@@ -1814,6 +1817,7 @@ function sanitizeUserConfig(raw, file, notes) {
|
|
|
1814
1817
|
if (threshold !== void 0 && !(typeof threshold === "number" && threshold > 0 && threshold <= 1)) {
|
|
1815
1818
|
drop("softThreshold", "must be a number above 0 and at most 1");
|
|
1816
1819
|
}
|
|
1820
|
+
if (config.updateCheck !== void 0 && typeof config.updateCheck !== "boolean") drop("updateCheck", "must be true or false");
|
|
1817
1821
|
for (const key of ["denyModels", "allowModels"]) {
|
|
1818
1822
|
const list = config[key];
|
|
1819
1823
|
if (list !== void 0 && !(Array.isArray(list) && list.every((item) => typeof item === "string"))) {
|
|
@@ -2542,8 +2546,10 @@ var SLASH_COMMANDS = [
|
|
|
2542
2546
|
{ name: "refresh-routing", description: "Recompute and apply OpenCode model routing", args: "refresh" },
|
|
2543
2547
|
{ name: "routing-status", description: "Show the model in use per role and its 5-hour quota use", args: "status" },
|
|
2544
2548
|
{ name: "routing-plan", description: "Switch the routing plan (go | go-plus | free) and rebuild", args: "plan $1" },
|
|
2545
|
-
{ name: "routing-eval", description: "Run the router scoring stress test", args: "eval" }
|
|
2549
|
+
{ name: "routing-eval", description: "Run the router scoring stress test", args: "eval" },
|
|
2550
|
+
{ name: "routing-update", description: "Update opencode-router to the newest version", args: "update" }
|
|
2546
2551
|
];
|
|
2552
|
+
var OUTPUT_BEGIN = "---BEGIN ROUTER OUTPUT---";
|
|
2547
2553
|
function cliScriptPath() {
|
|
2548
2554
|
return fileURLToPath(new URL("./bin.js", import.meta.url)).replace(/\\/g, "/");
|
|
2549
2555
|
}
|
|
@@ -2556,7 +2562,7 @@ function commandTemplate(args) {
|
|
|
2556
2562
|
"2. Present the text between the markers to the user exactly as written.",
|
|
2557
2563
|
"3. Do not summarize, reformat, shorten, or reorganize it.",
|
|
2558
2564
|
"",
|
|
2559
|
-
|
|
2565
|
+
OUTPUT_BEGIN,
|
|
2560
2566
|
`!\`node "${cliScriptPath()}" ${args} --scope global\``,
|
|
2561
2567
|
"---END ROUTER OUTPUT---"
|
|
2562
2568
|
].join("\n");
|
|
@@ -2593,6 +2599,9 @@ function ledgerFile() {
|
|
|
2593
2599
|
function evalReportFile() {
|
|
2594
2600
|
return path4.join(stateDir(), "eval-report.md");
|
|
2595
2601
|
}
|
|
2602
|
+
function updateCheckFile() {
|
|
2603
|
+
return path4.join(stateDir(), "update-check.json");
|
|
2604
|
+
}
|
|
2596
2605
|
function applyMarkerFile() {
|
|
2597
2606
|
return path4.join(stateDir(), "last-apply.json");
|
|
2598
2607
|
}
|
|
@@ -2739,6 +2748,97 @@ async function runEval(opts = {}) {
|
|
|
2739
2748
|
return 0;
|
|
2740
2749
|
}
|
|
2741
2750
|
|
|
2751
|
+
// src/update.ts
|
|
2752
|
+
import fs6 from "fs";
|
|
2753
|
+
import path5 from "path";
|
|
2754
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2755
|
+
var PACKAGE_NAME = "@s-berdia/opencode-router";
|
|
2756
|
+
var LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME.replace("/", "%2f")}/latest`;
|
|
2757
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2758
|
+
var TIMEOUT_MS = 3e3;
|
|
2759
|
+
function parseVersion(version) {
|
|
2760
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
2761
|
+
return match ? match.slice(1).map(Number) : null;
|
|
2762
|
+
}
|
|
2763
|
+
function isNewerVersion(a, b) {
|
|
2764
|
+
const left = parseVersion(a);
|
|
2765
|
+
const right = parseVersion(b);
|
|
2766
|
+
if (!left || !right) return false;
|
|
2767
|
+
for (let i = 0; i < 3; i++) {
|
|
2768
|
+
if (left[i] !== right[i]) return left[i] > right[i];
|
|
2769
|
+
}
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
function installedVersion() {
|
|
2773
|
+
const file = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
2774
|
+
return readJson(file)?.version ?? "0.0.0";
|
|
2775
|
+
}
|
|
2776
|
+
async function fetchLatestVersion() {
|
|
2777
|
+
const controller = new AbortController();
|
|
2778
|
+
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
2779
|
+
try {
|
|
2780
|
+
const res = await fetch(LATEST_URL, { signal: controller.signal, headers: { accept: "application/json" } });
|
|
2781
|
+
if (!res.ok) return void 0;
|
|
2782
|
+
const body = await res.json();
|
|
2783
|
+
return typeof body.version === "string" ? body.version : void 0;
|
|
2784
|
+
} catch {
|
|
2785
|
+
return void 0;
|
|
2786
|
+
} finally {
|
|
2787
|
+
clearTimeout(timeout);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
async function checkForUpdate(opts = {}) {
|
|
2791
|
+
try {
|
|
2792
|
+
if (loadUserConfig(opts.projectDir).config.updateCheck === false) return null;
|
|
2793
|
+
const current = opts.current ?? installedVersion();
|
|
2794
|
+
const now = opts.now ?? Date.now();
|
|
2795
|
+
const state = readJson(updateCheckFile());
|
|
2796
|
+
let latest = state?.latest;
|
|
2797
|
+
const fresh = state !== void 0 && now >= state.checkedAt && now - state.checkedAt < CHECK_INTERVAL_MS;
|
|
2798
|
+
if (!fresh) {
|
|
2799
|
+
latest = await fetchLatestVersion() ?? latest;
|
|
2800
|
+
writeJson(updateCheckFile(), { checkedAt: now, latest });
|
|
2801
|
+
}
|
|
2802
|
+
return latest && isNewerVersion(latest, current) ? { current, latest } : null;
|
|
2803
|
+
} catch {
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
function updateNotice(update) {
|
|
2808
|
+
return `opencode-router ${update.latest} is available (installed: ${update.current}). To update, type /routing-update in OpenCode.`;
|
|
2809
|
+
}
|
|
2810
|
+
function clearOutdatedCopies(latest) {
|
|
2811
|
+
const [scope, name] = PACKAGE_NAME.split("/");
|
|
2812
|
+
const scopeDir = path5.join(opencodeCacheDir(), "packages", scope);
|
|
2813
|
+
const result = { removed: [], pinned: [], current: [], failed: [] };
|
|
2814
|
+
let entries = [];
|
|
2815
|
+
try {
|
|
2816
|
+
entries = fs6.readdirSync(scopeDir);
|
|
2817
|
+
} catch {
|
|
2818
|
+
return result;
|
|
2819
|
+
}
|
|
2820
|
+
for (const entry of entries) {
|
|
2821
|
+
const dir = path5.join(scopeDir, entry);
|
|
2822
|
+
if (entry === name || entry === `${name}@latest`) {
|
|
2823
|
+
const manifest = path5.join(dir, "node_modules", scope, name, "package.json");
|
|
2824
|
+
const version = readJson(manifest)?.version ?? "0.0.0";
|
|
2825
|
+
if (!isNewerVersion(latest, version)) {
|
|
2826
|
+
result.current.push(dir);
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2829
|
+
try {
|
|
2830
|
+
fs6.rmSync(dir, { recursive: true, force: true, maxRetries: 3 });
|
|
2831
|
+
result.removed.push(dir);
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
result.failed.push({ dir, error: error instanceof Error ? error.message : String(error) });
|
|
2834
|
+
}
|
|
2835
|
+
} else if (entry.startsWith(`${name}@`) && isNewerVersion(latest, entry.slice(name.length + 1))) {
|
|
2836
|
+
result.pinned.push(entry);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
return result;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2742
2842
|
// src/cli.ts
|
|
2743
2843
|
var HELP = `opencode-router - dynamic model routing for OpenCode Go / Go Plus / Free
|
|
2744
2844
|
|
|
@@ -2749,6 +2849,7 @@ Usage:
|
|
|
2749
2849
|
opencode-router install [--scope] Write slash-command files and a starter config
|
|
2750
2850
|
opencode-router cat Show catalog metadata (path, date, model count)
|
|
2751
2851
|
opencode-router eval Run the scoring stress test
|
|
2852
|
+
opencode-router update Look for a newer version and clear OpenCode's cached copy
|
|
2752
2853
|
|
|
2753
2854
|
Options:
|
|
2754
2855
|
--plan <go|go-plus|free> Plan profile for this run
|
|
@@ -2813,6 +2914,12 @@ function parseCommandLine(argv, cwd) {
|
|
|
2813
2914
|
function build(opts) {
|
|
2814
2915
|
return buildRoutingTable({ plan: opts.plan, catalogFile: opts.catalog, projectDir: opts.cwd });
|
|
2815
2916
|
}
|
|
2917
|
+
async function writeUpdateNotice(opts) {
|
|
2918
|
+
const update = await checkForUpdate({ projectDir: opts.cwd });
|
|
2919
|
+
if (update) process.stdout.write(`
|
|
2920
|
+
${updateNotice(update)}
|
|
2921
|
+
`);
|
|
2922
|
+
}
|
|
2816
2923
|
async function cmdRefresh(opts) {
|
|
2817
2924
|
const { table } = await build(opts);
|
|
2818
2925
|
if (opts.json) {
|
|
@@ -2844,6 +2951,7 @@ Applied: ${detail}
|
|
|
2844
2951
|
"A running OpenCode session recomputes routing on its next message (within 15 s). Restart to reload the UI display now.\n"
|
|
2845
2952
|
);
|
|
2846
2953
|
}
|
|
2954
|
+
await writeUpdateNotice(opts);
|
|
2847
2955
|
}
|
|
2848
2956
|
return 0;
|
|
2849
2957
|
}
|
|
@@ -2857,6 +2965,7 @@ async function cmdStatus(opts) {
|
|
|
2857
2965
|
process.stdout.write(JSON.stringify({ table: result.table, rows, usage }, null, 2) + "\n");
|
|
2858
2966
|
} else {
|
|
2859
2967
|
process.stdout.write(formatStatus(result.table, rows) + "\n");
|
|
2968
|
+
await writeUpdateNotice(opts);
|
|
2860
2969
|
}
|
|
2861
2970
|
return 0;
|
|
2862
2971
|
}
|
|
@@ -2900,6 +3009,44 @@ function cmdInstall(opts) {
|
|
|
2900
3009
|
);
|
|
2901
3010
|
return 0;
|
|
2902
3011
|
}
|
|
3012
|
+
async function cmdUpdate() {
|
|
3013
|
+
const latest = await fetchLatestVersion();
|
|
3014
|
+
if (!latest) {
|
|
3015
|
+
process.stderr.write("error: could not reach the npm registry. Try again later.\n");
|
|
3016
|
+
return 1;
|
|
3017
|
+
}
|
|
3018
|
+
const { removed, pinned, current, failed } = clearOutdatedCopies(latest);
|
|
3019
|
+
if (failed.length > 0) {
|
|
3020
|
+
for (const { dir, error } of failed) process.stderr.write(`error: could not remove ${dir} (${error})
|
|
3021
|
+
`);
|
|
3022
|
+
process.stderr.write(`Close OpenCode, then run this command in a terminal: npx ${PACKAGE_NAME}@latest update
|
|
3023
|
+
`);
|
|
3024
|
+
return 1;
|
|
3025
|
+
}
|
|
3026
|
+
for (const dir of removed) process.stdout.write(`Removed the cached copy: ${dir}
|
|
3027
|
+
`);
|
|
3028
|
+
if (removed.length > 0) {
|
|
3029
|
+
process.stdout.write(`Restart OpenCode. It then installs opencode-router ${latest}.
|
|
3030
|
+
`);
|
|
3031
|
+
}
|
|
3032
|
+
if (pinned.length > 0) {
|
|
3033
|
+
process.stdout.write(
|
|
3034
|
+
`opencode.json pins an older version (${pinned.join(", ")}). Change the plugin entry to "${PACKAGE_NAME}@${latest}", then restart OpenCode.
|
|
3035
|
+
`
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3038
|
+
if (removed.length > 0 || pinned.length > 0) return 0;
|
|
3039
|
+
if (current.length > 0) {
|
|
3040
|
+
process.stdout.write(`OpenCode has opencode-router ${latest}, the newest version.
|
|
3041
|
+
`);
|
|
3042
|
+
} else {
|
|
3043
|
+
process.stdout.write(
|
|
3044
|
+
`OpenCode has no cached copy of opencode-router. The newest version is ${latest}. If you use a local build, run git pull and npm run build. If your OpenCode has the command "opencode plugin update", run it.
|
|
3045
|
+
`
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
return 0;
|
|
3049
|
+
}
|
|
2903
3050
|
function usageError(error) {
|
|
2904
3051
|
process.stderr.write(`error: ${error.message}
|
|
2905
3052
|
|
|
@@ -2927,6 +3074,8 @@ async function main(argv, cwd = process.cwd()) {
|
|
|
2927
3074
|
return cmdInstall(opts);
|
|
2928
3075
|
case "eval":
|
|
2929
3076
|
return await runEval({ plan: opts.plan, catalog: opts.catalog, json: opts.json, projectDir: opts.cwd });
|
|
3077
|
+
case "update":
|
|
3078
|
+
return await cmdUpdate();
|
|
2930
3079
|
default:
|
|
2931
3080
|
throw new UsageError(`unknown command "${command}"`);
|
|
2932
3081
|
}
|