@closedrouter/cli 1.1.8 → 1.1.10
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 +62 -0
- package/dist/bridge/launch-proxy.d.ts +1 -1
- package/dist/bridge/launch-proxy.d.ts.map +1 -1
- package/dist/client.d.ts +10 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/commands/agy-desktop.d.ts +12 -0
- package/dist/commands/agy-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.d.ts +5 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/cursor-desktop-hook.d.ts +3 -0
- package/dist/commands/cursor-desktop-hook.d.ts.map +1 -0
- package/dist/commands/cursor-desktop.d.ts +3 -0
- package/dist/commands/cursor-desktop.d.ts.map +1 -0
- package/dist/commands/cursor-launch.d.ts +6 -0
- package/dist/commands/cursor-launch.d.ts.map +1 -0
- package/dist/commands/desktop-inspector.d.ts +12 -0
- package/dist/commands/desktop-inspector.d.ts.map +1 -0
- package/dist/commands/desktop-launch.d.ts +33 -0
- package/dist/commands/desktop-launch.d.ts.map +1 -0
- package/dist/commands/settings.d.ts +6 -0
- package/dist/commands/settings.d.ts.map +1 -0
- package/dist/commands/start.d.ts +16 -2
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +1667 -50
- package/dist/model-picker.d.ts +56 -0
- package/dist/model-picker.d.ts.map +1 -0
- package/dist/terminal-ui.d.ts +22 -0
- package/dist/terminal-ui.d.ts.map +1 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -5137,6 +5137,13 @@ var require_websocket_server = __commonJS(function(exports, module) {
|
|
|
5137
5137
|
}
|
|
5138
5138
|
});
|
|
5139
5139
|
|
|
5140
|
+
// ../node_modules/.bun/emoji-regex@10.6.0/node_modules/emoji-regex/index.js
|
|
5141
|
+
var require_emoji_regex = __commonJS(function(exports, module) {
|
|
5142
|
+
module.exports = () => {
|
|
5143
|
+
return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
|
|
5144
|
+
};
|
|
5145
|
+
});
|
|
5146
|
+
|
|
5140
5147
|
// ../node_modules/.bun/commander@14.0.3/node_modules/commander/index.js
|
|
5141
5148
|
var exports_commander = {};
|
|
5142
5149
|
__exportCjs(exports_commander, {
|
|
@@ -5197,7 +5204,7 @@ var {
|
|
|
5197
5204
|
} = exports_commander;
|
|
5198
5205
|
|
|
5199
5206
|
// src/index.ts
|
|
5200
|
-
import { readFileSync as
|
|
5207
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
5201
5208
|
|
|
5202
5209
|
// ../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
|
|
5203
5210
|
import { stripVTControlCharacters as S2 } from "node:util";
|
|
@@ -6261,6 +6268,10 @@ class ClosedRouterClient {
|
|
|
6261
6268
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6262
6269
|
import { homedir } from "node:os";
|
|
6263
6270
|
import { join } from "node:path";
|
|
6271
|
+
var MODEL_GROUPINGS = ["model-provider", "account-provider", "none"];
|
|
6272
|
+
function isModelGrouping(value) {
|
|
6273
|
+
return MODEL_GROUPINGS.some((grouping) => grouping === value);
|
|
6274
|
+
}
|
|
6264
6275
|
var DEFAULT_SERVER_URL = "https://closedrouter.213.183.59.186.sslip.io";
|
|
6265
6276
|
function getConfigDir() {
|
|
6266
6277
|
return process.env.CLOSEDROUTER_CONFIG_DIR || join(homedir(), ".closedrouter");
|
|
@@ -6281,7 +6292,9 @@ function loadConfig() {
|
|
|
6281
6292
|
sessionToken: parsed.sessionToken,
|
|
6282
6293
|
apiKey: parsed.apiKey,
|
|
6283
6294
|
bridgeApiKey: parsed.bridgeApiKey,
|
|
6284
|
-
user: parsed.user
|
|
6295
|
+
user: parsed.user,
|
|
6296
|
+
modelGrouping: isModelGrouping(parsed.modelGrouping) ? parsed.modelGrouping : undefined,
|
|
6297
|
+
favoriteModels: Array.isArray(parsed.favoriteModels) ? [...new Set(parsed.favoriteModels.filter((id) => typeof id === "string" && id.length > 0))] : undefined
|
|
6285
6298
|
};
|
|
6286
6299
|
} catch {
|
|
6287
6300
|
return { serverUrl: DEFAULT_SERVER_URL };
|
|
@@ -8643,26 +8656,778 @@ to scan and import local subscriptions!`, "Pool Empty");
|
|
|
8643
8656
|
|
|
8644
8657
|
// src/commands/start.ts
|
|
8645
8658
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
8646
|
-
import {
|
|
8647
|
-
import {
|
|
8648
|
-
import {
|
|
8649
|
-
import {
|
|
8650
|
-
import {
|
|
8659
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
8660
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
8661
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
8662
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8663
|
+
import { homedir as homedir14 } from "node:os";
|
|
8664
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
8665
|
+
|
|
8666
|
+
// src/model-picker.ts
|
|
8667
|
+
import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
|
|
8668
|
+
|
|
8669
|
+
// ../node_modules/.bun/ansi-regex@6.4.0/node_modules/ansi-regex/index.js
|
|
8670
|
+
function ansiRegex({ onlyFirst = false } = {}) {
|
|
8671
|
+
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
|
|
8672
|
+
const osc = `(?:(?:\\u001B\\]|\\u009D)[^\\u0007\\u001B\\u009C\\u009D]*${ST})`;
|
|
8673
|
+
const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
|
|
8674
|
+
const pattern = `${osc}|${csi}`;
|
|
8675
|
+
return new RegExp(pattern, onlyFirst ? undefined : "g");
|
|
8676
|
+
}
|
|
8677
|
+
|
|
8678
|
+
// ../node_modules/.bun/strip-ansi@7.2.0/node_modules/strip-ansi/index.js
|
|
8679
|
+
var regex = ansiRegex();
|
|
8680
|
+
function stripAnsi(string) {
|
|
8681
|
+
if (typeof string !== "string") {
|
|
8682
|
+
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
|
|
8683
|
+
}
|
|
8684
|
+
if (!string.includes("\x1B") && !string.includes("")) {
|
|
8685
|
+
return string;
|
|
8686
|
+
}
|
|
8687
|
+
return string.replace(regex, "");
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8690
|
+
// ../node_modules/.bun/get-east-asian-width@1.7.0/node_modules/get-east-asian-width/lookup-data.js
|
|
8691
|
+
var ambiguousMinimalCodePoint = 161;
|
|
8692
|
+
var ambiguousMaximumCodePoint = 1114109;
|
|
8693
|
+
var ambiguousRanges = [161, 161, 164, 164, 167, 168, 170, 170, 173, 174, 176, 180, 182, 186, 188, 191, 198, 198, 208, 208, 215, 216, 222, 225, 230, 230, 232, 234, 236, 237, 240, 240, 242, 243, 247, 250, 252, 252, 254, 254, 257, 257, 273, 273, 275, 275, 283, 283, 294, 295, 299, 299, 305, 307, 312, 312, 319, 322, 324, 324, 328, 331, 333, 333, 338, 339, 358, 359, 363, 363, 462, 462, 464, 464, 466, 466, 468, 468, 470, 470, 472, 472, 474, 474, 476, 476, 593, 593, 609, 609, 708, 708, 711, 711, 713, 715, 717, 717, 720, 720, 728, 731, 733, 733, 735, 735, 768, 879, 913, 929, 931, 937, 945, 961, 963, 969, 1025, 1025, 1040, 1103, 1105, 1105, 8208, 8208, 8211, 8214, 8216, 8217, 8220, 8221, 8224, 8226, 8228, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8254, 8254, 8308, 8308, 8319, 8319, 8321, 8324, 8364, 8364, 8451, 8451, 8453, 8453, 8457, 8457, 8467, 8467, 8470, 8470, 8481, 8482, 8486, 8486, 8491, 8491, 8531, 8532, 8539, 8542, 8544, 8555, 8560, 8569, 8585, 8585, 8592, 8601, 8632, 8633, 8658, 8658, 8660, 8660, 8679, 8679, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9449, 9451, 9547, 9552, 9587, 9600, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 9886, 9887, 9919, 9919, 9926, 9933, 9935, 9939, 9941, 9953, 9955, 9955, 9960, 9961, 9963, 9969, 9972, 9972, 9974, 9977, 9979, 9980, 9982, 9983, 10045, 10045, 10102, 10111, 11094, 11097, 12872, 12879, 57344, 63743, 65024, 65039, 65533, 65533, 127232, 127242, 127248, 127277, 127280, 127337, 127344, 127373, 127375, 127376, 127387, 127404, 917760, 917999, 983040, 1048573, 1048576, 1114109];
|
|
8694
|
+
var fullwidthMinimalCodePoint = 12288;
|
|
8695
|
+
var fullwidthMaximumCodePoint = 65510;
|
|
8696
|
+
var fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510];
|
|
8697
|
+
var wideMinimalCodePoint = 4352;
|
|
8698
|
+
var wideMaximumCodePoint = 262141;
|
|
8699
|
+
var wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101594, 101631, 101664, 101760, 101874, 101888, 102801, 102816, 102866, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110888, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110952, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127406, 127406, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128729, 128732, 128735, 128747, 128748, 128756, 128764, 128986, 128986, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129734, 129736, 129736, 129740, 129757, 129759, 129771, 129775, 129786, 131072, 196605, 196608, 262141];
|
|
8700
|
+
|
|
8701
|
+
// ../node_modules/.bun/get-east-asian-width@1.7.0/node_modules/get-east-asian-width/utilities.js
|
|
8702
|
+
var isInRange = (ranges, codePoint) => {
|
|
8703
|
+
let low = 0;
|
|
8704
|
+
let high = Math.floor(ranges.length / 2) - 1;
|
|
8705
|
+
while (low <= high) {
|
|
8706
|
+
const mid = Math.floor((low + high) / 2);
|
|
8707
|
+
const i = mid * 2;
|
|
8708
|
+
if (codePoint < ranges[i]) {
|
|
8709
|
+
high = mid - 1;
|
|
8710
|
+
} else if (codePoint > ranges[i + 1]) {
|
|
8711
|
+
low = mid + 1;
|
|
8712
|
+
} else {
|
|
8713
|
+
return true;
|
|
8714
|
+
}
|
|
8715
|
+
}
|
|
8716
|
+
return false;
|
|
8717
|
+
};
|
|
8718
|
+
|
|
8719
|
+
// ../node_modules/.bun/get-east-asian-width@1.7.0/node_modules/get-east-asian-width/lookup.js
|
|
8720
|
+
var commonCjkCodePoint = 19968;
|
|
8721
|
+
var [wideFastPathStart, wideFastPathEnd] = /* @__PURE__ */ findWideFastPathRange(wideRanges);
|
|
8722
|
+
function findWideFastPathRange(ranges) {
|
|
8723
|
+
let fastPathStart = ranges[0];
|
|
8724
|
+
let fastPathEnd = ranges[1];
|
|
8725
|
+
for (let index = 0;index < ranges.length; index += 2) {
|
|
8726
|
+
const start = ranges[index];
|
|
8727
|
+
const end = ranges[index + 1];
|
|
8728
|
+
if (commonCjkCodePoint >= start && commonCjkCodePoint <= end) {
|
|
8729
|
+
return [start, end];
|
|
8730
|
+
}
|
|
8731
|
+
if (end - start > fastPathEnd - fastPathStart) {
|
|
8732
|
+
fastPathStart = start;
|
|
8733
|
+
fastPathEnd = end;
|
|
8734
|
+
}
|
|
8735
|
+
}
|
|
8736
|
+
return [fastPathStart, fastPathEnd];
|
|
8737
|
+
}
|
|
8738
|
+
var isAmbiguous = (codePoint) => {
|
|
8739
|
+
if (codePoint < ambiguousMinimalCodePoint || codePoint > ambiguousMaximumCodePoint) {
|
|
8740
|
+
return false;
|
|
8741
|
+
}
|
|
8742
|
+
return isInRange(ambiguousRanges, codePoint);
|
|
8743
|
+
};
|
|
8744
|
+
var isFullwidth = (codePoint) => {
|
|
8745
|
+
if (codePoint < fullwidthMinimalCodePoint || codePoint > fullwidthMaximumCodePoint) {
|
|
8746
|
+
return false;
|
|
8747
|
+
}
|
|
8748
|
+
return isInRange(fullwidthRanges, codePoint);
|
|
8749
|
+
};
|
|
8750
|
+
var isWide = (codePoint) => {
|
|
8751
|
+
if (codePoint >= wideFastPathStart && codePoint <= wideFastPathEnd) {
|
|
8752
|
+
return true;
|
|
8753
|
+
}
|
|
8754
|
+
if (codePoint < wideMinimalCodePoint || codePoint > wideMaximumCodePoint) {
|
|
8755
|
+
return false;
|
|
8756
|
+
}
|
|
8757
|
+
return isInRange(wideRanges, codePoint);
|
|
8758
|
+
};
|
|
8759
|
+
|
|
8760
|
+
// ../node_modules/.bun/get-east-asian-width@1.7.0/node_modules/get-east-asian-width/index.js
|
|
8761
|
+
function validate(codePoint) {
|
|
8762
|
+
if (!Number.isSafeInteger(codePoint)) {
|
|
8763
|
+
throw new TypeError(`Expected a code point, got \`${typeof codePoint}\`.`);
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
function eastAsianWidth(codePoint, { ambiguousAsWide = false } = {}) {
|
|
8767
|
+
validate(codePoint);
|
|
8768
|
+
if (isFullwidth(codePoint) || isWide(codePoint) || ambiguousAsWide && isAmbiguous(codePoint)) {
|
|
8769
|
+
return 2;
|
|
8770
|
+
}
|
|
8771
|
+
return 1;
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
// ../node_modules/.bun/string-width@7.2.0/node_modules/string-width/index.js
|
|
8775
|
+
var import_emoji_regex = __toESM(require_emoji_regex(), 1);
|
|
8776
|
+
var segmenter = new Intl.Segmenter;
|
|
8777
|
+
var defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
8778
|
+
function stringWidth(string, options = {}) {
|
|
8779
|
+
if (typeof string !== "string" || string.length === 0) {
|
|
8780
|
+
return 0;
|
|
8781
|
+
}
|
|
8782
|
+
const {
|
|
8783
|
+
ambiguousIsNarrow = true,
|
|
8784
|
+
countAnsiEscapeCodes = false
|
|
8785
|
+
} = options;
|
|
8786
|
+
if (!countAnsiEscapeCodes) {
|
|
8787
|
+
string = stripAnsi(string);
|
|
8788
|
+
}
|
|
8789
|
+
if (string.length === 0) {
|
|
8790
|
+
return 0;
|
|
8791
|
+
}
|
|
8792
|
+
let width = 0;
|
|
8793
|
+
const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
|
|
8794
|
+
for (const { segment: character } of segmenter.segment(string)) {
|
|
8795
|
+
const codePoint = character.codePointAt(0);
|
|
8796
|
+
if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) {
|
|
8797
|
+
continue;
|
|
8798
|
+
}
|
|
8799
|
+
if (codePoint >= 8203 && codePoint <= 8207 || codePoint === 65279) {
|
|
8800
|
+
continue;
|
|
8801
|
+
}
|
|
8802
|
+
if (codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65056 && codePoint <= 65071) {
|
|
8803
|
+
continue;
|
|
8804
|
+
}
|
|
8805
|
+
if (codePoint >= 55296 && codePoint <= 57343) {
|
|
8806
|
+
continue;
|
|
8807
|
+
}
|
|
8808
|
+
if (codePoint >= 65024 && codePoint <= 65039) {
|
|
8809
|
+
continue;
|
|
8810
|
+
}
|
|
8811
|
+
if (defaultIgnorableCodePointRegex.test(character)) {
|
|
8812
|
+
continue;
|
|
8813
|
+
}
|
|
8814
|
+
if (import_emoji_regex.default().test(character)) {
|
|
8815
|
+
width += 2;
|
|
8816
|
+
continue;
|
|
8817
|
+
}
|
|
8818
|
+
width += eastAsianWidth(codePoint, eastAsianWidthOptions);
|
|
8819
|
+
}
|
|
8820
|
+
return width;
|
|
8821
|
+
}
|
|
8822
|
+
|
|
8823
|
+
// src/terminal-ui.ts
|
|
8824
|
+
var PALETTE = {
|
|
8825
|
+
text: { rgb: [252, 252, 254], indexed: 255, ansi: 97 },
|
|
8826
|
+
muted: { rgb: [160, 161, 163], indexed: 247, ansi: 37 },
|
|
8827
|
+
border: { rgb: [83, 98, 107], indexed: 241, ansi: 90 },
|
|
8828
|
+
accent: { rgb: [212, 254, 81], indexed: 191, ansi: 92 },
|
|
8829
|
+
gold: { rgb: [255, 213, 128], indexed: 222, ansi: 93 },
|
|
8830
|
+
teal: { rgb: [108, 216, 213], indexed: 80, ansi: 96 },
|
|
8831
|
+
blue: { rgb: [120, 185, 255], indexed: 111, ansi: 94 },
|
|
8832
|
+
warm: { rgb: [244, 179, 147], indexed: 216, ansi: 93 },
|
|
8833
|
+
violet: { rgb: [200, 164, 244], indexed: 183, ansi: 95 },
|
|
8834
|
+
key: { rgb: [252, 252, 254], indexed: 255, ansi: 97 }
|
|
8835
|
+
};
|
|
8836
|
+
var CANVAS = [4, 8, 10];
|
|
8837
|
+
var SELECTION = [31, 44, 21];
|
|
8838
|
+
var KEY_SURFACE = [38, 52, 59];
|
|
8839
|
+
var segmenter2 = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
8840
|
+
function terminalText(text) {
|
|
8841
|
+
return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, "");
|
|
8842
|
+
}
|
|
8843
|
+
function span(text, tone = "text", bold = false) {
|
|
8844
|
+
return { text: terminalText(text), tone, bold };
|
|
8845
|
+
}
|
|
8846
|
+
function append(line, text, source) {
|
|
8847
|
+
const last = line.at(-1);
|
|
8848
|
+
if (last?.tone === source.tone && last.bold === source.bold)
|
|
8849
|
+
last.text += text;
|
|
8850
|
+
else
|
|
8851
|
+
line.push({ ...source, text });
|
|
8852
|
+
}
|
|
8853
|
+
function lineWidth(line) {
|
|
8854
|
+
return line.reduce((width, part) => width + stringWidth(part.text), 0);
|
|
8855
|
+
}
|
|
8856
|
+
function fitLine(line, width, ellipsis = true) {
|
|
8857
|
+
if (width < 1)
|
|
8858
|
+
return [];
|
|
8859
|
+
const result = [];
|
|
8860
|
+
const clipped = lineWidth(line) > width;
|
|
8861
|
+
const limit = clipped && ellipsis ? width - 1 : width;
|
|
8862
|
+
let used = 0;
|
|
8863
|
+
outer:
|
|
8864
|
+
for (const part of line) {
|
|
8865
|
+
for (const { segment } of segmenter2.segment(part.text)) {
|
|
8866
|
+
const size = stringWidth(segment);
|
|
8867
|
+
if (used + size > limit)
|
|
8868
|
+
break outer;
|
|
8869
|
+
append(result, segment, part);
|
|
8870
|
+
used += size;
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
if (clipped && ellipsis) {
|
|
8874
|
+
result.push(span("…", "muted"));
|
|
8875
|
+
used++;
|
|
8876
|
+
}
|
|
8877
|
+
if (used < width)
|
|
8878
|
+
result.push(span(" ".repeat(width - used)));
|
|
8879
|
+
return result;
|
|
8880
|
+
}
|
|
8881
|
+
function wrapLine(line, width, continuation = 0) {
|
|
8882
|
+
width = Math.max(1, width);
|
|
8883
|
+
const lines = [];
|
|
8884
|
+
let current = [];
|
|
8885
|
+
let used = 0;
|
|
8886
|
+
for (const part of line) {
|
|
8887
|
+
for (const { segment } of segmenter2.segment(part.text)) {
|
|
8888
|
+
const size = stringWidth(segment);
|
|
8889
|
+
if (used + size > width && used > 0) {
|
|
8890
|
+
lines.push(current);
|
|
8891
|
+
const indent = Math.min(continuation, Math.max(0, width - size));
|
|
8892
|
+
current = indent ? [span(" ".repeat(indent))] : [];
|
|
8893
|
+
used = indent;
|
|
8894
|
+
}
|
|
8895
|
+
const visible = size > width ? "?" : segment;
|
|
8896
|
+
append(current, visible, part);
|
|
8897
|
+
used += size > width ? 1 : size;
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
lines.push(current);
|
|
8901
|
+
return lines;
|
|
8902
|
+
}
|
|
8903
|
+
function terminalColorMode(env = process.env) {
|
|
8904
|
+
if (env.NO_COLOR !== undefined || env.FORCE_COLOR === "0" || env.TERM === "dumb")
|
|
8905
|
+
return "none";
|
|
8906
|
+
if (env.FORCE_COLOR === "3")
|
|
8907
|
+
return "truecolor";
|
|
8908
|
+
if (env.FORCE_COLOR === "2")
|
|
8909
|
+
return "256";
|
|
8910
|
+
if (env.FORCE_COLOR !== undefined)
|
|
8911
|
+
return "ansi";
|
|
8912
|
+
if (!process.stdout.isTTY)
|
|
8913
|
+
return "none";
|
|
8914
|
+
const depth = process.stdout.getColorDepth?.(env) || 4;
|
|
8915
|
+
return depth >= 24 ? "truecolor" : depth >= 8 ? "256" : depth >= 4 ? "ansi" : "none";
|
|
8916
|
+
}
|
|
8917
|
+
|
|
8918
|
+
class TerminalTheme {
|
|
8919
|
+
mode;
|
|
8920
|
+
constructor(mode = terminalColorMode()) {
|
|
8921
|
+
this.mode = mode;
|
|
8922
|
+
}
|
|
8923
|
+
color(tone, selected) {
|
|
8924
|
+
const swatch = PALETTE[tone];
|
|
8925
|
+
const rgb = selected && tone === "muted" ? [202, 213, 191] : swatch.rgb;
|
|
8926
|
+
if (this.mode === "truecolor")
|
|
8927
|
+
return `38;2;${rgb.join(";")}`;
|
|
8928
|
+
if (this.mode === "256")
|
|
8929
|
+
return `38;5;${swatch.indexed}`;
|
|
8930
|
+
return String(swatch.ansi);
|
|
8931
|
+
}
|
|
8932
|
+
paint(line, width, selected = false) {
|
|
8933
|
+
return fitLine(line, width).map((part) => {
|
|
8934
|
+
if (this.mode === "none")
|
|
8935
|
+
return part.text;
|
|
8936
|
+
const rgb = selected ? SELECTION : part.tone === "key" ? KEY_SURFACE : CANVAS;
|
|
8937
|
+
const bg = this.mode === "truecolor" ? `48;2;${rgb.join(";")}` : this.mode === "256" ? `48;5;${selected ? 22 : part.tone === "key" ? 236 : 232}` : String(selected || part.tone === "key" ? 100 : 40);
|
|
8938
|
+
return `\x1B[${this.color(part.tone, selected)};${bg}${part.bold ? ";1" : ""}m${part.text}\x1B[0m`;
|
|
8939
|
+
}).join("");
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
function providerTone(provider) {
|
|
8943
|
+
const name = provider.toLowerCase();
|
|
8944
|
+
if (/anthropic|claude/.test(name))
|
|
8945
|
+
return "warm";
|
|
8946
|
+
if (/openai|codex/.test(name))
|
|
8947
|
+
return "teal";
|
|
8948
|
+
if (/google|gemini|antigravity/.test(name))
|
|
8949
|
+
return "blue";
|
|
8950
|
+
if (/cursor/.test(name))
|
|
8951
|
+
return "violet";
|
|
8952
|
+
return "text";
|
|
8953
|
+
}
|
|
8954
|
+
|
|
8955
|
+
// src/model-picker.ts
|
|
8956
|
+
var MODEL_GROUPING_LABELS = {
|
|
8957
|
+
"model-provider": "Model provider",
|
|
8958
|
+
"account-provider": "Account provider",
|
|
8959
|
+
none: "No grouping"
|
|
8960
|
+
};
|
|
8961
|
+
var PROVIDER_LABELS = {
|
|
8962
|
+
anthropic: "Anthropic",
|
|
8963
|
+
openai: "OpenAI",
|
|
8964
|
+
google: "Google",
|
|
8965
|
+
xai: "xAI",
|
|
8966
|
+
"x-ai": "xAI",
|
|
8967
|
+
cursor: "Cursor",
|
|
8968
|
+
custom: "Other models",
|
|
8969
|
+
antigravity_oauth: "Antigravity",
|
|
8970
|
+
claude_oauth: "Claude Code",
|
|
8971
|
+
codex_oauth: "Codex",
|
|
8972
|
+
cursor_oauth: "Cursor",
|
|
8973
|
+
cursor_sdk: "Cursor SDK",
|
|
8974
|
+
grok_oauth: "Grok",
|
|
8975
|
+
openai_api: "OpenAI API",
|
|
8976
|
+
anthropic_api: "Anthropic API",
|
|
8977
|
+
claude_api: "Claude API",
|
|
8978
|
+
google_api: "Google API",
|
|
8979
|
+
gemini_api: "Gemini API",
|
|
8980
|
+
xai_api: "xAI API"
|
|
8981
|
+
};
|
|
8982
|
+
function providerLabel(provider) {
|
|
8983
|
+
return PROVIDER_LABELS[provider] || provider;
|
|
8984
|
+
}
|
|
8985
|
+
function gatewayModelId(model) {
|
|
8986
|
+
return model.id || model.name || "";
|
|
8987
|
+
}
|
|
8988
|
+
function preparePickerModels(models) {
|
|
8989
|
+
const unique = new Map;
|
|
8990
|
+
for (const model of models) {
|
|
8991
|
+
const id = gatewayModelId(model);
|
|
8992
|
+
if (!id)
|
|
8993
|
+
continue;
|
|
8994
|
+
const existing = unique.get(id);
|
|
8995
|
+
unique.set(id, {
|
|
8996
|
+
id,
|
|
8997
|
+
name: model.name || id,
|
|
8998
|
+
owner: model.owned_by || existing?.owner || "Unknown model provider",
|
|
8999
|
+
providers: [...new Set([...existing?.providers || [], ...model.provider_types || []])].sort()
|
|
9000
|
+
});
|
|
9001
|
+
}
|
|
9002
|
+
return [...unique.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
9003
|
+
}
|
|
9004
|
+
function modelProviderLabel(model) {
|
|
9005
|
+
const providers = model.providers.map(providerLabel);
|
|
9006
|
+
return terminalText(`${model.id} [${providers.length ? providers.join(", ") : "provider info unavailable"}]`);
|
|
9007
|
+
}
|
|
9008
|
+
|
|
9009
|
+
class ModelPickerState {
|
|
9010
|
+
models;
|
|
9011
|
+
query = "";
|
|
9012
|
+
grouping;
|
|
9013
|
+
favorites;
|
|
9014
|
+
entries = [];
|
|
9015
|
+
cursor = 0;
|
|
9016
|
+
constructor(models, grouping = "none", favorites = []) {
|
|
9017
|
+
this.models = models;
|
|
9018
|
+
this.grouping = grouping;
|
|
9019
|
+
this.favorites = new Set(favorites);
|
|
9020
|
+
this.refresh();
|
|
9021
|
+
}
|
|
9022
|
+
get selected() {
|
|
9023
|
+
return this.entries[this.cursor];
|
|
9024
|
+
}
|
|
9025
|
+
refresh() {
|
|
9026
|
+
const selectedKey = this.selected?.key;
|
|
9027
|
+
const terms = this.query.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
9028
|
+
const groups = new Map;
|
|
9029
|
+
for (const model of this.models) {
|
|
9030
|
+
const searchable = [
|
|
9031
|
+
model.id,
|
|
9032
|
+
model.name,
|
|
9033
|
+
model.owner,
|
|
9034
|
+
providerLabel(model.owner),
|
|
9035
|
+
...model.providers,
|
|
9036
|
+
...model.providers.map(providerLabel)
|
|
9037
|
+
].join(" ").toLowerCase();
|
|
9038
|
+
if (!terms.every((term) => searchable.includes(term)))
|
|
9039
|
+
continue;
|
|
9040
|
+
const groupNames = this.grouping === "model-provider" ? [providerLabel(model.owner)] : this.grouping === "account-provider" ? model.providers.map(providerLabel) : [""];
|
|
9041
|
+
for (const group of new Set(groupNames.length ? groupNames : ["Unknown account provider"])) {
|
|
9042
|
+
const members = groups.get(group) || [];
|
|
9043
|
+
members.push(model);
|
|
9044
|
+
groups.set(group, members);
|
|
9045
|
+
}
|
|
9046
|
+
}
|
|
9047
|
+
this.entries = [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).flatMap(([group, models]) => models.sort((a, b) => Number(this.favorites.has(b.id)) - Number(this.favorites.has(a.id)) || a.id.localeCompare(b.id)).map((model) => ({ key: `${group}\x00${model.id}`, group, model })));
|
|
9048
|
+
this.cursor = Math.max(0, this.entries.findIndex((entry) => entry.key === selectedKey));
|
|
9049
|
+
}
|
|
9050
|
+
handleKey(text, key, pageSize = 8) {
|
|
9051
|
+
if (key.ctrl && (key.name === "c" || key.name === "d"))
|
|
9052
|
+
return { type: "cancel" };
|
|
9053
|
+
if (key.name === "escape") {
|
|
9054
|
+
if (!this.query)
|
|
9055
|
+
return { type: "cancel" };
|
|
9056
|
+
this.query = "";
|
|
9057
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
9058
|
+
return this.selected ? { type: "select", model: this.selected.model.id } : undefined;
|
|
9059
|
+
} else if (["up", "down", "pageup", "pagedown", "home", "end"].includes(key.name || "")) {
|
|
9060
|
+
const count = this.entries.length;
|
|
9061
|
+
if (!count)
|
|
9062
|
+
return;
|
|
9063
|
+
if (key.name === "home")
|
|
9064
|
+
this.cursor = 0;
|
|
9065
|
+
else if (key.name === "end")
|
|
9066
|
+
this.cursor = count - 1;
|
|
9067
|
+
else if (key.name === "pageup")
|
|
9068
|
+
this.cursor = Math.max(0, this.cursor - pageSize);
|
|
9069
|
+
else if (key.name === "pagedown")
|
|
9070
|
+
this.cursor = Math.min(count - 1, this.cursor + pageSize);
|
|
9071
|
+
else
|
|
9072
|
+
this.cursor = (this.cursor + (key.name === "up" ? -1 : 1) + count) % count;
|
|
9073
|
+
return;
|
|
9074
|
+
} else if (key.name === "tab") {
|
|
9075
|
+
const selectedId = this.selected?.model.id;
|
|
9076
|
+
this.grouping = MODEL_GROUPINGS[(MODEL_GROUPINGS.indexOf(this.grouping) + 1) % MODEL_GROUPINGS.length];
|
|
9077
|
+
this.refresh();
|
|
9078
|
+
this.cursor = Math.max(0, this.entries.findIndex((entry) => entry.model.id === selectedId));
|
|
9079
|
+
return { type: "grouping" };
|
|
9080
|
+
} else if (text === "*" && !key.ctrl && !key.meta) {
|
|
9081
|
+
if (!this.selected)
|
|
9082
|
+
return;
|
|
9083
|
+
const id = this.selected.model.id;
|
|
9084
|
+
if (this.favorites.has(id))
|
|
9085
|
+
this.favorites.delete(id);
|
|
9086
|
+
else
|
|
9087
|
+
this.favorites.add(id);
|
|
9088
|
+
this.refresh();
|
|
9089
|
+
return { type: "favorites" };
|
|
9090
|
+
} else if (key.name === "backspace") {
|
|
9091
|
+
this.query = Array.from(this.query).slice(0, -1).join("");
|
|
9092
|
+
} else if (key.ctrl && key.name === "u") {
|
|
9093
|
+
this.query = "";
|
|
9094
|
+
} else if (text && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f-\x9f]/.test(text)) {
|
|
9095
|
+
this.query += text;
|
|
9096
|
+
} else {
|
|
9097
|
+
return;
|
|
9098
|
+
}
|
|
9099
|
+
this.refresh();
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
function matchingText(text, tone, query, bold = false) {
|
|
9103
|
+
const terms = query.trim().split(/\s+/).filter(Boolean).map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
9104
|
+
if (!terms.length)
|
|
9105
|
+
return [span(text, tone, bold)];
|
|
9106
|
+
const pattern = new RegExp(terms.join("|"), "gi");
|
|
9107
|
+
const result = [];
|
|
9108
|
+
let offset = 0;
|
|
9109
|
+
for (const match of text.matchAll(pattern)) {
|
|
9110
|
+
const index = match.index;
|
|
9111
|
+
if (index > offset)
|
|
9112
|
+
result.push(span(text.slice(offset, index), tone, bold));
|
|
9113
|
+
result.push(span(match[0], "accent", true));
|
|
9114
|
+
offset = index + match[0].length;
|
|
9115
|
+
}
|
|
9116
|
+
if (offset < text.length)
|
|
9117
|
+
result.push(span(text.slice(offset), tone, bold));
|
|
9118
|
+
return result;
|
|
9119
|
+
}
|
|
9120
|
+
function modelRows(state, width) {
|
|
9121
|
+
const result = [];
|
|
9122
|
+
let lastGroup;
|
|
9123
|
+
const groupCounts = new Map;
|
|
9124
|
+
for (const entry of state.entries)
|
|
9125
|
+
groupCounts.set(entry.group, (groupCounts.get(entry.group) || 0) + 1);
|
|
9126
|
+
for (const [index, entry] of state.entries.entries()) {
|
|
9127
|
+
if (entry.group && entry.group !== lastGroup) {
|
|
9128
|
+
if (result.length)
|
|
9129
|
+
result.push({ line: [], active: false });
|
|
9130
|
+
result.push({ line: [span(` ${entry.group}`, providerTone(entry.group), true), span(` ${groupCounts.get(entry.group)}`, "muted")], active: false });
|
|
9131
|
+
lastGroup = entry.group;
|
|
9132
|
+
}
|
|
9133
|
+
const active = index === state.cursor;
|
|
9134
|
+
const label = [
|
|
9135
|
+
span(active ? "> " : " ", "accent", true),
|
|
9136
|
+
span(state.favorites.has(entry.model.id) ? "★" : " ", "gold"),
|
|
9137
|
+
span(" "),
|
|
9138
|
+
...matchingText(entry.model.id, "text", state.query, active),
|
|
9139
|
+
span(" [", "border")
|
|
9140
|
+
];
|
|
9141
|
+
if (entry.model.providers.length) {
|
|
9142
|
+
entry.model.providers.forEach((provider, i) => {
|
|
9143
|
+
if (i)
|
|
9144
|
+
label.push(span(", ", "muted"));
|
|
9145
|
+
label.push(...matchingText(providerLabel(provider), providerTone(provider), state.query));
|
|
9146
|
+
});
|
|
9147
|
+
} else
|
|
9148
|
+
label.push(span("provider info unavailable", "muted"));
|
|
9149
|
+
label.push(span("]", "border"));
|
|
9150
|
+
result.push(...wrapLine(label, width, 4).map((line) => ({ line, active })));
|
|
9151
|
+
}
|
|
9152
|
+
return result;
|
|
9153
|
+
}
|
|
9154
|
+
function viewport(body, height) {
|
|
9155
|
+
const first = Math.max(0, body.findIndex((row) => row.active));
|
|
9156
|
+
let last = first;
|
|
9157
|
+
while (body[last + 1]?.active)
|
|
9158
|
+
last++;
|
|
9159
|
+
const preferred = last - first + 1 > height ? first : Math.max(last - height + 1, first - Math.floor(height / 2));
|
|
9160
|
+
const start = Math.max(0, Math.min(Math.max(0, body.length - height), preferred));
|
|
9161
|
+
return { rows: body.slice(start, start + height), start };
|
|
9162
|
+
}
|
|
9163
|
+
function balanced(left, right, width) {
|
|
9164
|
+
const rightSize = Math.min(lineWidth(right), Math.floor(width / 2));
|
|
9165
|
+
return [...fitLine(left, width - rightSize - 1), span(" "), ...fitLine(right, rightSize)];
|
|
9166
|
+
}
|
|
9167
|
+
function keyHint(key, label) {
|
|
9168
|
+
return [span(`[${key}]`, "key", true), span(` ${label} `, "muted")];
|
|
9169
|
+
}
|
|
9170
|
+
function hintRows(hints, width) {
|
|
9171
|
+
const rows = [];
|
|
9172
|
+
let current = [];
|
|
9173
|
+
for (const hint of hints) {
|
|
9174
|
+
if (current.length && lineWidth(current) + lineWidth(hint) > width) {
|
|
9175
|
+
rows.push(current);
|
|
9176
|
+
current = [];
|
|
9177
|
+
}
|
|
9178
|
+
if (lineWidth(hint) > width) {
|
|
9179
|
+
rows.push(...wrapLine(hint, width));
|
|
9180
|
+
} else
|
|
9181
|
+
current.push(...hint);
|
|
9182
|
+
}
|
|
9183
|
+
if (current.length)
|
|
9184
|
+
rows.push(current);
|
|
9185
|
+
return rows;
|
|
9186
|
+
}
|
|
9187
|
+
function panelRule(width, label = "", bottom = false) {
|
|
9188
|
+
const title = label ? fitLine([span(` ${label} `, "muted")], Math.min(width - 4, lineWidth([span(` ${label} `)]))) : [];
|
|
9189
|
+
return [
|
|
9190
|
+
span(bottom ? "╰" : "╭", "border"),
|
|
9191
|
+
span("─", "border"),
|
|
9192
|
+
...title,
|
|
9193
|
+
span("─".repeat(Math.max(0, width - lineWidth(title) - 3)), "border"),
|
|
9194
|
+
span(bottom ? "╯" : "╮", "border")
|
|
9195
|
+
];
|
|
9196
|
+
}
|
|
9197
|
+
function detailRows(state, width) {
|
|
9198
|
+
const model = state.selected?.model;
|
|
9199
|
+
if (!model)
|
|
9200
|
+
return [[span("No model selected", "muted")], [], [span("Clear the search to browse", "muted")]];
|
|
9201
|
+
const rows = [
|
|
9202
|
+
...wrapLine([span(model.id, "text", true)], width),
|
|
9203
|
+
[],
|
|
9204
|
+
[span("Model provider", "muted")],
|
|
9205
|
+
[span(providerLabel(model.owner), providerTone(model.owner), true)],
|
|
9206
|
+
[],
|
|
9207
|
+
[span("Account providers", "muted")]
|
|
9208
|
+
];
|
|
9209
|
+
if (model.providers.length) {
|
|
9210
|
+
for (const provider of model.providers)
|
|
9211
|
+
rows.push(...wrapLine([span("• ", "border"), span(providerLabel(provider), providerTone(provider))], width, 2));
|
|
9212
|
+
} else
|
|
9213
|
+
rows.push(...wrapLine([span("Provider info unavailable", "muted")], width));
|
|
9214
|
+
rows.push([], [span(state.favorites.has(model.id) ? "★ Saved to favorites" : "☆ Add to favorites with *", "gold")], [], [span("Automatic pool routing", "muted")]);
|
|
9215
|
+
return rows;
|
|
9216
|
+
}
|
|
9217
|
+
function renderModelPicker(state, columns = 80, rows = 24, options = {}) {
|
|
9218
|
+
const theme = options.theme || new TerminalTheme;
|
|
9219
|
+
const width = Math.max(1, Math.min(120, columns - 1));
|
|
9220
|
+
const height = Math.max(1, rows - 1);
|
|
9221
|
+
const count = new Set(state.entries.map((entry) => entry.model.id)).size;
|
|
9222
|
+
const favorites = state.models.filter((model) => state.favorites.has(model.id)).length;
|
|
9223
|
+
const status = [
|
|
9224
|
+
span(`${count} of ${state.models.length} models`, "muted"),
|
|
9225
|
+
span(` · ★ ${favorites}`, "gold"),
|
|
9226
|
+
span(state.entries.length ? ` · ${state.cursor + 1}/${state.entries.length}` : "", "muted")
|
|
9227
|
+
];
|
|
9228
|
+
const search = [span("Search: ", "muted"), span(state.query || "Type to filter models or providers", state.query ? "text" : "muted"), span("▏", "accent")];
|
|
9229
|
+
const compact = width < 72 || height < 18;
|
|
9230
|
+
const brand = [span("CLOSED", "text", true), span("ROUTER", "accent", true)];
|
|
9231
|
+
const header = [];
|
|
9232
|
+
if (width < 24 || height < 7) {
|
|
9233
|
+
const minimal = modelRows(state, width);
|
|
9234
|
+
const top = height > 2 ? [theme.paint(search, width)] : [];
|
|
9235
|
+
const visible = viewport(minimal, Math.max(1, height - top.length - (height > 3 ? 1 : 0)));
|
|
9236
|
+
const body = visible.rows.length ? visible.rows.map((row) => theme.paint(row.line, width, row.active)) : [theme.paint([span("No matching models", "warm")], width)];
|
|
9237
|
+
return [...top, ...body, ...height > 3 ? [theme.paint(status, width)] : []].slice(0, height).join(`
|
|
9238
|
+
`);
|
|
9239
|
+
}
|
|
9240
|
+
if (compact) {
|
|
9241
|
+
header.push(balanced(brand, [span("Model pool", "muted")], width), search, [span("Grouping: ", "muted"), span(MODEL_GROUPING_LABELS[state.grouping], "accent", true)]);
|
|
9242
|
+
} else {
|
|
9243
|
+
header.push(balanced([...brand, span(" / ", "border"), span("Model pool", "muted")], [span(options.toolLabel || "Choose a model", "text")], width), [], panelRule(width), [span("│", "border"), span(" / ", "accent"), ...fitLine(search, width - 5), span("│", "border")], panelRule(width, "", true));
|
|
9244
|
+
const tabs = [span("Grouping: ", "muted")];
|
|
9245
|
+
for (const grouping of MODEL_GROUPINGS) {
|
|
9246
|
+
const active = grouping === state.grouping;
|
|
9247
|
+
tabs.push(span(active ? `[ ${MODEL_GROUPING_LABELS[grouping]} ]` : ` ${MODEL_GROUPING_LABELS[grouping]} `, active ? "accent" : "muted", active), span(" "));
|
|
9248
|
+
}
|
|
9249
|
+
header.push(tabs, []);
|
|
9250
|
+
}
|
|
9251
|
+
const keys = [keyHint("Enter", "start"), keyHint("↑/↓", "move"), keyHint("*", "favorite"), keyHint("Tab", "group")];
|
|
9252
|
+
const secondary = [keyHint("Esc", "clear/cancel"), keyHint("Ctrl+U", "clear")];
|
|
9253
|
+
if (!compact)
|
|
9254
|
+
secondary.push(keyHint("Backspace", "edit"));
|
|
9255
|
+
const footer = [status, ...hintRows(keys, width), ...hintRows(secondary, width)];
|
|
9256
|
+
const footerBudget = Math.max(1, height - header.length - 3);
|
|
9257
|
+
footer.splice(footerBudget);
|
|
9258
|
+
const panelHeight = Math.max(1, height - header.length - footer.length);
|
|
9259
|
+
const framed = panelHeight >= 5 && width >= 36;
|
|
9260
|
+
const bodyHeight = Math.max(1, panelHeight - (framed ? 2 : 0));
|
|
9261
|
+
const detailWidth = !compact && width >= 100 && bodyHeight >= 10 ? 30 : 0;
|
|
9262
|
+
const listWidth = width - (detailWidth ? detailWidth + 2 : 0);
|
|
9263
|
+
const contentWidth = listWidth - (framed ? 5 : 0);
|
|
9264
|
+
let body = modelRows(state, contentWidth);
|
|
9265
|
+
if (!body.length) {
|
|
9266
|
+
body = [
|
|
9267
|
+
[],
|
|
9268
|
+
[span("No matching models", "warm", true)],
|
|
9269
|
+
[],
|
|
9270
|
+
...wrapLine([span("Try a shorter model or provider name.", "muted")], contentWidth),
|
|
9271
|
+
[],
|
|
9272
|
+
[...keyHint("Ctrl+U", "clear search")]
|
|
9273
|
+
].map((line) => ({ line, active: false }));
|
|
9274
|
+
}
|
|
9275
|
+
const visible = viewport(body, bodyHeight);
|
|
9276
|
+
const details = detailWidth ? detailRows(state, detailWidth - 4) : [];
|
|
9277
|
+
const frame = header.map((line) => theme.paint(line, width));
|
|
9278
|
+
const joinPanels = (left, right) => left + (detailWidth ? theme.paint([span(" ")], 2) + right : "");
|
|
9279
|
+
if (framed)
|
|
9280
|
+
frame.push(joinPanels(theme.paint(panelRule(listWidth, state.selected?.group ? `Models · ${state.selected.group}` : "Models"), listWidth), detailWidth ? theme.paint(panelRule(detailWidth, "Selected model"), detailWidth) : ""));
|
|
9281
|
+
const scrolling = body.length > bodyHeight;
|
|
9282
|
+
const thumbSize = Math.max(1, Math.floor(bodyHeight * bodyHeight / body.length));
|
|
9283
|
+
const thumbStart = scrolling ? Math.round(visible.start / (body.length - bodyHeight) * (bodyHeight - thumbSize)) : 0;
|
|
9284
|
+
for (let i = 0;i < bodyHeight; i++) {
|
|
9285
|
+
const row = visible.rows[i] || { line: [], active: false };
|
|
9286
|
+
const thumb = scrolling && i >= thumbStart && i < thumbStart + thumbSize;
|
|
9287
|
+
const bar = span(scrolling ? thumb ? "┃" : "│" : " ", thumb ? "accent" : "border");
|
|
9288
|
+
const left = framed ? theme.paint([span("│", "border"), span(" ")], 2) + theme.paint(row.line, contentWidth, row.active) + theme.paint([bar, span(" "), span("│", "border")], 3) : theme.paint(row.line, listWidth, row.active);
|
|
9289
|
+
const right = detailWidth ? theme.paint([span("│", "border"), span(" "), ...fitLine(details[i] || [], detailWidth - 4), span(" "), span("│", "border")], detailWidth) : "";
|
|
9290
|
+
frame.push(joinPanels(left, right));
|
|
9291
|
+
}
|
|
9292
|
+
if (framed)
|
|
9293
|
+
frame.push(joinPanels(theme.paint(panelRule(listWidth, "", true), listWidth), detailWidth ? theme.paint(panelRule(detailWidth, "", true), detailWidth) : ""));
|
|
9294
|
+
frame.push(...footer.map((line) => theme.paint(line, width)));
|
|
9295
|
+
return frame.slice(0, height).join(`
|
|
9296
|
+
`);
|
|
9297
|
+
}
|
|
9298
|
+
async function selectPoolModel(models, options = {}) {
|
|
9299
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
9300
|
+
throw new Error("Model selection requires an interactive terminal. Pass --model <id>.");
|
|
9301
|
+
const state = new ModelPickerState(preparePickerModels(models), options.grouping, options.favorites);
|
|
9302
|
+
if (!state.models.length)
|
|
9303
|
+
throw new Error("No models are available to select.");
|
|
9304
|
+
const input = process.stdin;
|
|
9305
|
+
const output = process.stdout;
|
|
9306
|
+
const wasRaw = input.isRaw;
|
|
9307
|
+
const wasFlowing = input.readableFlowing === true;
|
|
9308
|
+
let cleanupPicker;
|
|
9309
|
+
let chosen;
|
|
9310
|
+
let frameLines = 0;
|
|
9311
|
+
let previousFrame = "";
|
|
9312
|
+
const alternateScreen = process.env.TERM !== "dumb";
|
|
9313
|
+
const theme = new TerminalTheme;
|
|
9314
|
+
const draw = () => {
|
|
9315
|
+
const frame = renderModelPicker(state, output.columns || 80, output.rows || 24, { theme, toolLabel: options.toolLabel });
|
|
9316
|
+
if (frame === previousFrame)
|
|
9317
|
+
return;
|
|
9318
|
+
if (alternateScreen)
|
|
9319
|
+
output.write("\x1B[H\x1B[0J");
|
|
9320
|
+
else if (frameLines)
|
|
9321
|
+
output.write(`\r${frameLines > 1 ? `\x1B[${frameLines - 1}A` : ""}\x1B[0J`);
|
|
9322
|
+
output.write(frame);
|
|
9323
|
+
frameLines = frame.split(`
|
|
9324
|
+
`).length;
|
|
9325
|
+
previousFrame = frame;
|
|
9326
|
+
};
|
|
9327
|
+
emitKeypressEvents2(input);
|
|
9328
|
+
try {
|
|
9329
|
+
return await new Promise((resolve, reject) => {
|
|
9330
|
+
const cancel = () => reject(new Error("Start cancelled."));
|
|
9331
|
+
const onKey = (text, key) => {
|
|
9332
|
+
try {
|
|
9333
|
+
const action = state.handleKey(text, key, Math.max(1, (output.rows || 24) - 8));
|
|
9334
|
+
if (action?.type === "select") {
|
|
9335
|
+
chosen = state.selected?.model;
|
|
9336
|
+
resolve(action.model);
|
|
9337
|
+
} else if (action?.type === "cancel")
|
|
9338
|
+
cancel();
|
|
9339
|
+
else {
|
|
9340
|
+
if (action?.type === "favorites")
|
|
9341
|
+
options.onFavoritesChange?.([...state.favorites].sort());
|
|
9342
|
+
if (action?.type === "grouping")
|
|
9343
|
+
options.onGroupingChange?.(state.grouping);
|
|
9344
|
+
draw();
|
|
9345
|
+
}
|
|
9346
|
+
} catch (error) {
|
|
9347
|
+
reject(error);
|
|
9348
|
+
}
|
|
9349
|
+
};
|
|
9350
|
+
const onError = (error) => reject(error);
|
|
9351
|
+
const cleanup = () => {
|
|
9352
|
+
input.off("keypress", onKey);
|
|
9353
|
+
input.off("end", cancel);
|
|
9354
|
+
input.off("error", onError);
|
|
9355
|
+
output.off("resize", draw);
|
|
9356
|
+
process.off("SIGINT", cancel);
|
|
9357
|
+
process.off("SIGTERM", cancel);
|
|
9358
|
+
};
|
|
9359
|
+
cleanupPicker = cleanup;
|
|
9360
|
+
input.on("keypress", onKey);
|
|
9361
|
+
input.once("end", cancel);
|
|
9362
|
+
input.once("error", onError);
|
|
9363
|
+
output.on("resize", draw);
|
|
9364
|
+
process.once("SIGINT", cancel);
|
|
9365
|
+
process.once("SIGTERM", cancel);
|
|
9366
|
+
input.setRawMode(true);
|
|
9367
|
+
input.resume();
|
|
9368
|
+
output.write(`${alternateScreen ? "\x1B[?1049h" : ""}\x1B[?25l`);
|
|
9369
|
+
draw();
|
|
9370
|
+
});
|
|
9371
|
+
} finally {
|
|
9372
|
+
cleanupPicker?.();
|
|
9373
|
+
input.setRawMode(wasRaw);
|
|
9374
|
+
if (!wasFlowing)
|
|
9375
|
+
input.pause();
|
|
9376
|
+
if (alternateScreen)
|
|
9377
|
+
output.write("\x1B[?1049l");
|
|
9378
|
+
else if (frameLines)
|
|
9379
|
+
output.write(`\r${frameLines > 1 ? `\x1B[${frameLines - 1}A` : ""}\x1B[0J`);
|
|
9380
|
+
output.write(`\x1B[?25h${chosen ? `Selected ${modelProviderLabel(chosen)}` : "Model selection cancelled."}
|
|
9381
|
+
`);
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
|
|
9385
|
+
// src/commands/settings.ts
|
|
9386
|
+
function parseModelGrouping(value) {
|
|
9387
|
+
if (!isModelGrouping(value)) {
|
|
9388
|
+
throw new Error(`Unknown model grouping '${value}'. Choose: ${MODEL_GROUPINGS.join(", ")}.`);
|
|
9389
|
+
}
|
|
9390
|
+
return value;
|
|
9391
|
+
}
|
|
9392
|
+
async function settingsCommand(options = {}) {
|
|
9393
|
+
let grouping;
|
|
9394
|
+
if (options.modelGrouping !== undefined) {
|
|
9395
|
+
grouping = parseModelGrouping(options.modelGrouping);
|
|
9396
|
+
} else {
|
|
9397
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
9398
|
+
throw new Error("Choose a grouping with `cr settings --model-grouping <model-provider|account-provider|none>`.");
|
|
9399
|
+
}
|
|
9400
|
+
const choice = await ve({
|
|
9401
|
+
message: "How should the model selector group models?",
|
|
9402
|
+
initialValue: loadConfig().modelGrouping || "none",
|
|
9403
|
+
options: [
|
|
9404
|
+
{ value: "model-provider", label: MODEL_GROUPING_LABELS["model-provider"], hint: "Group by the model's owner, such as OpenAI or Anthropic" },
|
|
9405
|
+
{ value: "account-provider", label: MODEL_GROUPING_LABELS["account-provider"], hint: "Group by pool provider, such as Codex or Antigravity" },
|
|
9406
|
+
{ value: "none", label: MODEL_GROUPING_LABELS.none, hint: "One model list" }
|
|
9407
|
+
]
|
|
9408
|
+
});
|
|
9409
|
+
if (pD(choice))
|
|
9410
|
+
throw new Error("Settings cancelled.");
|
|
9411
|
+
grouping = parseModelGrouping(String(choice));
|
|
9412
|
+
}
|
|
9413
|
+
saveConfig({ modelGrouping: grouping });
|
|
9414
|
+
M2.success(`Model grouping saved: ${MODEL_GROUPING_LABELS[grouping]}`);
|
|
9415
|
+
}
|
|
8651
9416
|
|
|
8652
9417
|
// src/bridge/launch-proxy.ts
|
|
8653
9418
|
import { randomUUID } from "node:crypto";
|
|
8654
9419
|
import { createServer as createServer2, request as httpRequest } from "node:http";
|
|
8655
9420
|
import { request as httpsRequest } from "node:https";
|
|
8656
9421
|
var HOP_HEADERS = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host"]);
|
|
8657
|
-
async function startLaunchProxy(gatewayUrl, apiKey, harness) {
|
|
9422
|
+
async function startLaunchProxy(gatewayUrl, apiKey, harness, localKey = apiKey, modelOverride) {
|
|
8658
9423
|
const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
|
|
8659
9424
|
if (!["http:", "https:"].includes(upstream.protocol) || upstream.username || upstream.password) {
|
|
8660
9425
|
throw new Error("ClosedRouter server URL must use HTTP or HTTPS without embedded credentials.");
|
|
8661
9426
|
}
|
|
8662
9427
|
const launchId = randomUUID();
|
|
8663
9428
|
const transport = upstream.protocol === "https:" ? httpsRequest : httpRequest;
|
|
8664
|
-
const server = createServer2((incoming, outgoing) => {
|
|
8665
|
-
const authorized = incoming.headers.authorization === `Bearer ${
|
|
9429
|
+
const server = createServer2(async (incoming, outgoing) => {
|
|
9430
|
+
const authorized = incoming.headers.authorization === `Bearer ${localKey}` || incoming.headers["x-api-key"] === localKey || incoming.headers["x-goog-api-key"] === localKey;
|
|
8666
9431
|
if (!authorized) {
|
|
8667
9432
|
outgoing.writeHead(401).end();
|
|
8668
9433
|
return;
|
|
@@ -8675,6 +9440,32 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness) {
|
|
|
8675
9440
|
const local = new URL(incoming.url, "http://localhost");
|
|
8676
9441
|
target.pathname = upstream.pathname.replace(/\/$/, "") + local.pathname;
|
|
8677
9442
|
target.search = local.search;
|
|
9443
|
+
let rewrittenBody;
|
|
9444
|
+
if (modelOverride && incoming.method === "POST" && /^\/v1\/(?:messages(?:\/count_tokens)?|chat\/completions|responses)$/.test(local.pathname)) {
|
|
9445
|
+
try {
|
|
9446
|
+
const chunks = [];
|
|
9447
|
+
let length = 0;
|
|
9448
|
+
for await (const chunk of incoming) {
|
|
9449
|
+
length += chunk.length;
|
|
9450
|
+
if (length > 32 * 1024 * 1024) {
|
|
9451
|
+
outgoing.writeHead(413).end();
|
|
9452
|
+
return;
|
|
9453
|
+
}
|
|
9454
|
+
chunks.push(Buffer.from(chunk));
|
|
9455
|
+
}
|
|
9456
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
9457
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
9458
|
+
outgoing.writeHead(400).end();
|
|
9459
|
+
return;
|
|
9460
|
+
}
|
|
9461
|
+
const payload = body;
|
|
9462
|
+
const tinyProbe = local.pathname === "/v1/messages" && !modelOverride.startsWith("claude-") && payload.model !== modelOverride && typeof payload.max_tokens === "number" && payload.max_tokens <= 16 && Array.isArray(payload.messages) && payload.messages.length === 1 && (!Array.isArray(payload.tools) || payload.tools.length === 0);
|
|
9463
|
+
rewrittenBody = JSON.stringify({ ...payload, model: modelOverride, ...tinyProbe ? { max_tokens: 512 } : {} });
|
|
9464
|
+
} catch {
|
|
9465
|
+
outgoing.writeHead(400).end();
|
|
9466
|
+
return;
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
8678
9469
|
const headers = { ...incoming.headers };
|
|
8679
9470
|
const connectionHeaders = String(headers.connection || "").split(",").map((x) => x.trim().toLowerCase());
|
|
8680
9471
|
for (const key of Object.keys(headers)) {
|
|
@@ -8683,6 +9474,13 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness) {
|
|
|
8683
9474
|
}
|
|
8684
9475
|
headers["x-closedrouter-harness"] = harness;
|
|
8685
9476
|
headers["x-closedrouter-launch-id"] = launchId;
|
|
9477
|
+
if (localKey !== apiKey) {
|
|
9478
|
+
delete headers["x-api-key"];
|
|
9479
|
+
delete headers["x-goog-api-key"];
|
|
9480
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
9481
|
+
}
|
|
9482
|
+
if (rewrittenBody !== undefined)
|
|
9483
|
+
delete headers["content-length"];
|
|
8686
9484
|
const request = transport(target, { method: incoming.method, headers }, (response) => {
|
|
8687
9485
|
const responseHeaders = { ...response.headers };
|
|
8688
9486
|
for (const key of HOP_HEADERS)
|
|
@@ -8702,7 +9500,10 @@ async function startLaunchProxy(gatewayUrl, apiKey, harness) {
|
|
|
8702
9500
|
if (!outgoing.writableFinished)
|
|
8703
9501
|
request.destroy();
|
|
8704
9502
|
});
|
|
8705
|
-
|
|
9503
|
+
if (rewrittenBody !== undefined)
|
|
9504
|
+
request.end(rewrittenBody);
|
|
9505
|
+
else
|
|
9506
|
+
incoming.pipe(request);
|
|
8706
9507
|
});
|
|
8707
9508
|
await new Promise((resolve, reject) => {
|
|
8708
9509
|
server.once("error", reject);
|
|
@@ -8819,6 +9620,718 @@ function registerAgyLaunchModel(homeDir, model, transportModel) {
|
|
|
8819
9620
|
return label;
|
|
8820
9621
|
}
|
|
8821
9622
|
|
|
9623
|
+
// src/commands/cursor-launch.ts
|
|
9624
|
+
import { execFileSync } from "node:child_process";
|
|
9625
|
+
import { homedir as homedir9 } from "node:os";
|
|
9626
|
+
import { join as join10 } from "node:path";
|
|
9627
|
+
var CURSOR_LOCAL_INSTALL_URL = "https://cursor.com/internal-local-install-nHZIc6RZgTXIfai0aO?darwin=true&channel=prod";
|
|
9628
|
+
var MANAGED_OPTIONS = new Set([
|
|
9629
|
+
"--model",
|
|
9630
|
+
"--base-url",
|
|
9631
|
+
"--local-agent-api-key",
|
|
9632
|
+
"--api-key",
|
|
9633
|
+
"--auth-token",
|
|
9634
|
+
"--endpoint",
|
|
9635
|
+
"-e",
|
|
9636
|
+
"--agent-endpoint",
|
|
9637
|
+
"--enable-bedrock",
|
|
9638
|
+
"--enable-local-bedrock"
|
|
9639
|
+
]);
|
|
9640
|
+
function buildCursorLaunchPlan(serverUrl, apiKey, model, toolArgs = [], homeDir = homedir9(), executablePath = "agent-local") {
|
|
9641
|
+
const conflicting = toolArgs.find((arg) => MANAGED_OPTIONS.has(arg.split("=", 1)[0]) || /^-e[^-]/.test(arg));
|
|
9642
|
+
if (conflicting) {
|
|
9643
|
+
const option = conflicting.startsWith("-e") && !conflicting.startsWith("--") ? "-e" : conflicting.split("=", 1)[0];
|
|
9644
|
+
throw new Error(`Remove ${option}; ClosedRouter manages the Cursor model, provider, and authentication. Put other arguments after --.`);
|
|
9645
|
+
}
|
|
9646
|
+
const env = { ...process.env };
|
|
9647
|
+
for (const name of [
|
|
9648
|
+
"CURSOR_API_KEY",
|
|
9649
|
+
"CURSOR_AUTH_TOKEN",
|
|
9650
|
+
"CURSOR_ENABLE_BEDROCK",
|
|
9651
|
+
"CURSOR_ENABLE_LOCAL_BEDROCK",
|
|
9652
|
+
"CURSOR_LOCAL_AGENT_API_KEY_HELPER",
|
|
9653
|
+
"CURSOR_LOCAL_AGENT_ALLOW_CURSOR_HOST"
|
|
9654
|
+
])
|
|
9655
|
+
delete env[name];
|
|
9656
|
+
env.CURSOR_LOCAL_AGENT_BASE_URL = `${serverUrl.replace(/\/+$/, "")}/v1`;
|
|
9657
|
+
env.CURSOR_LOCAL_AGENT_API_KEY = apiKey;
|
|
9658
|
+
env.CURSOR_ENABLE_AUTHLESS = "1";
|
|
9659
|
+
env.CURSOR_CONFIG_DIR = join10(process.env.CLOSEDROUTER_CONFIG_DIR || join10(homeDir, ".closedrouter"), "cursor-launch");
|
|
9660
|
+
env.CURSOR_DATA_DIR = env.CURSOR_CONFIG_DIR;
|
|
9661
|
+
return { tool: "cursor", command: executablePath, args: ["--authless", "--model", model, ...toolArgs], env };
|
|
9662
|
+
}
|
|
9663
|
+
function resolveCursorLocalExecutable(executablePath, homeDir = homedir9()) {
|
|
9664
|
+
const candidates = executablePath ? [executablePath] : [
|
|
9665
|
+
"agent-local",
|
|
9666
|
+
"cursor-agent-local",
|
|
9667
|
+
join10(homeDir, ".local", "bin", "agent-local"),
|
|
9668
|
+
join10(homeDir, ".local", "bin", "cursor-agent-local")
|
|
9669
|
+
];
|
|
9670
|
+
const env = buildCursorLaunchPlan("http://127.0.0.1:1", "cr-sk-preflight", "preflight", [], homeDir).env;
|
|
9671
|
+
for (const command of candidates) {
|
|
9672
|
+
try {
|
|
9673
|
+
execFileSync(command, ["--authless", "about", "--format", "json"], {
|
|
9674
|
+
env,
|
|
9675
|
+
timeout: 1e4,
|
|
9676
|
+
stdio: "ignore"
|
|
9677
|
+
});
|
|
9678
|
+
return command;
|
|
9679
|
+
} catch {}
|
|
9680
|
+
}
|
|
9681
|
+
throw new Error(`Cursor requires the separate Agent Local package (agent-local or cursor-agent-local).${executablePath ? ` '${executablePath}' could not start in authless mode.` : ""} Install it from ${CURSOR_LOCAL_INSTALL_URL}, or pass --path <cursor-agent-local>. The regular Cursor agent CLI cannot use the ClosedRouter pool.`);
|
|
9682
|
+
}
|
|
9683
|
+
|
|
9684
|
+
// src/commands/desktop-launch.ts
|
|
9685
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
9686
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9687
|
+
import { accessSync, closeSync, constants, existsSync as existsSync11, mkdirSync as mkdirSync2, mkdtempSync, openSync, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9688
|
+
import { homedir as homedir10 } from "node:os";
|
|
9689
|
+
import { dirname, isAbsolute, join as join11, relative, resolve } from "node:path";
|
|
9690
|
+
|
|
9691
|
+
// src/commands/desktop-inspector.ts
|
|
9692
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
|
|
9693
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9694
|
+
async function installDesktopInspectorHook(hookPath, logPath, signal, options = {}) {
|
|
9695
|
+
const deadline = Date.now() + 15000;
|
|
9696
|
+
let target;
|
|
9697
|
+
while (Date.now() < deadline && !signal.aborted) {
|
|
9698
|
+
if (existsSync10(logPath))
|
|
9699
|
+
target = /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[^\s]+)/.exec(readFileSync9(logPath, "utf8"))?.[1];
|
|
9700
|
+
if (target)
|
|
9701
|
+
break;
|
|
9702
|
+
await delay(100, undefined, { signal });
|
|
9703
|
+
}
|
|
9704
|
+
if (!target)
|
|
9705
|
+
throw new Error("Desktop app did not expose its launch inspector. Close the paused app and retry with --open-only.");
|
|
9706
|
+
const Socket = "Bun" in globalThis ? globalThis.WebSocket : wrapper_default;
|
|
9707
|
+
const socket = new Socket(target);
|
|
9708
|
+
let sequence = 0;
|
|
9709
|
+
const pending = new Map;
|
|
9710
|
+
let resolvePaused = () => {};
|
|
9711
|
+
const paused = new Promise((resolve) => {
|
|
9712
|
+
resolvePaused = resolve;
|
|
9713
|
+
});
|
|
9714
|
+
const timer = setTimeout(() => socket.close(), Math.max(1, deadline - Date.now()));
|
|
9715
|
+
const abort = () => socket.close();
|
|
9716
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
9717
|
+
const closed = () => {
|
|
9718
|
+
for (const item of pending.values())
|
|
9719
|
+
item.reject(new Error("Desktop app launch inspector disconnected before the hook was installed."));
|
|
9720
|
+
pending.clear();
|
|
9721
|
+
};
|
|
9722
|
+
socket.addEventListener("close", closed);
|
|
9723
|
+
socket.addEventListener("message", (event) => {
|
|
9724
|
+
const message = JSON.parse(String(event.data));
|
|
9725
|
+
if (message.method === "Debugger.paused")
|
|
9726
|
+
resolvePaused();
|
|
9727
|
+
if (message.id !== undefined) {
|
|
9728
|
+
const item = pending.get(message.id);
|
|
9729
|
+
if (!item)
|
|
9730
|
+
return;
|
|
9731
|
+
pending.delete(message.id);
|
|
9732
|
+
if (message.error)
|
|
9733
|
+
item.reject(new Error(message.error.message));
|
|
9734
|
+
else
|
|
9735
|
+
item.resolve(message.result || {});
|
|
9736
|
+
}
|
|
9737
|
+
});
|
|
9738
|
+
const rpc = (method, params = {}) => new Promise((resolve, reject) => {
|
|
9739
|
+
const id = ++sequence;
|
|
9740
|
+
pending.set(id, { resolve, reject });
|
|
9741
|
+
socket.send(JSON.stringify({ id, method, params }));
|
|
9742
|
+
});
|
|
9743
|
+
try {
|
|
9744
|
+
await new Promise((resolve, reject) => {
|
|
9745
|
+
socket.addEventListener("open", () => resolve(), { once: true });
|
|
9746
|
+
socket.addEventListener("error", () => reject(new Error("Could not connect to Desktop app's launch inspector.")), { once: true });
|
|
9747
|
+
socket.addEventListener("close", () => reject(new Error("Desktop app's launch inspector closed.")), { once: true });
|
|
9748
|
+
});
|
|
9749
|
+
await rpc("Debugger.enable");
|
|
9750
|
+
if (options.startupBreakpoint)
|
|
9751
|
+
await rpc("Debugger.setBreakpointByUrl", options.startupBreakpoint);
|
|
9752
|
+
await rpc("Runtime.runIfWaitingForDebugger");
|
|
9753
|
+
await Promise.race([paused, delay(Math.max(1, deadline - Date.now()), undefined, { signal }).then(() => {
|
|
9754
|
+
throw new Error("Desktop app did not pause before loading its application.");
|
|
9755
|
+
})]);
|
|
9756
|
+
const result = await rpc("Runtime.evaluate", {
|
|
9757
|
+
expression: `process.getBuiltinModule('module').createRequire(process.cwd() + '/closedrouter-hook.cjs')(${JSON.stringify(hookPath)}); 'closedrouter-hook-loaded'`,
|
|
9758
|
+
returnByValue: true
|
|
9759
|
+
});
|
|
9760
|
+
if (result.exceptionDetails || result.result?.value !== "closedrouter-hook-loaded")
|
|
9761
|
+
throw new Error("Could not install Desktop app's request hook. Close the paused app and use --open-only.");
|
|
9762
|
+
await rpc("Debugger.resume");
|
|
9763
|
+
if (options.readyPath) {
|
|
9764
|
+
while (!existsSync10(options.readyPath) && Date.now() < deadline)
|
|
9765
|
+
await delay(25, undefined, { signal });
|
|
9766
|
+
if (!existsSync10(options.readyPath))
|
|
9767
|
+
throw new Error("The desktop app did not enable its local provider runtime.");
|
|
9768
|
+
}
|
|
9769
|
+
await rpc("Runtime.evaluate", { expression: "setTimeout(() => process.getBuiltinModule('inspector').close(), 100).unref(); true", returnByValue: true });
|
|
9770
|
+
} finally {
|
|
9771
|
+
clearTimeout(timer);
|
|
9772
|
+
signal.removeEventListener("abort", abort);
|
|
9773
|
+
socket.close();
|
|
9774
|
+
}
|
|
9775
|
+
}
|
|
9776
|
+
|
|
9777
|
+
// src/commands/desktop-launch.ts
|
|
9778
|
+
var APP_NAMES = {
|
|
9779
|
+
codex: ["Codex", "ChatGPT"],
|
|
9780
|
+
claude: ["Claude"],
|
|
9781
|
+
cursor: ["Cursor"],
|
|
9782
|
+
agy: ["Antigravity", "Antigravity IDE"]
|
|
9783
|
+
};
|
|
9784
|
+
function isDesktopTool(tool) {
|
|
9785
|
+
return Object.hasOwn(APP_NAMES, tool);
|
|
9786
|
+
}
|
|
9787
|
+
function resolveDesktopExecutable(tool, explicitPath, platform = process.platform, home = homedir10()) {
|
|
9788
|
+
if (explicitPath) {
|
|
9789
|
+
const path = resolve(explicitPath);
|
|
9790
|
+
if (!existsSync11(path))
|
|
9791
|
+
throw new Error(`Desktop app not found at ${path}.`);
|
|
9792
|
+
if (platform === "darwin" && path.endsWith(".app"))
|
|
9793
|
+
return path;
|
|
9794
|
+
accessSync(path, constants.X_OK);
|
|
9795
|
+
return path;
|
|
9796
|
+
}
|
|
9797
|
+
const names = APP_NAMES[tool];
|
|
9798
|
+
const candidates = platform === "darwin" ? names.flatMap((name) => [join11("/Applications", `${name}.app`), join11(home, "Applications", `${name}.app`)]) : platform === "win32" ? names.flatMap((name) => [
|
|
9799
|
+
join11(process.env.LOCALAPPDATA || join11(home, "AppData", "Local"), "Programs", name, `${name}.exe`),
|
|
9800
|
+
join11(process.env.ProgramFiles || "C:\\Program Files", name, `${name}.exe`)
|
|
9801
|
+
]) : names.flatMap((name) => [join11("/opt", name.toLowerCase(), name.toLowerCase()), join11("/usr/bin", `${name.toLowerCase()}-desktop`)]);
|
|
9802
|
+
const installed = candidates.find((path) => existsSync11(path));
|
|
9803
|
+
if (!installed)
|
|
9804
|
+
throw new Error(`${names[0]} desktop app was not found. Pass --path <app-bundle-or-executable>.`);
|
|
9805
|
+
return installed;
|
|
9806
|
+
}
|
|
9807
|
+
function validateDesktopArgs(args) {
|
|
9808
|
+
const managed = ["--user-data-dir", "--profile", "--config", "--model", "--boot-1p-once", "--inspect", "--inspect-brk", "-p", "-c", "-m"];
|
|
9809
|
+
const conflict = args.find((arg) => managed.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
9810
|
+
if (conflict)
|
|
9811
|
+
throw new Error(`Remove ${conflict}; ClosedRouter manages the desktop profile and model.`);
|
|
9812
|
+
}
|
|
9813
|
+
function createDesktopProfile(tool) {
|
|
9814
|
+
const root = join11(getConfigDir(), "desktop", tool);
|
|
9815
|
+
mkdirSync2(root, { recursive: true, mode: 448 });
|
|
9816
|
+
return mkdtempSync(join11(root, "session-"));
|
|
9817
|
+
}
|
|
9818
|
+
function buildDesktopLaunchPlan(tool, executable, profileDir, baseUrl, localKey, model, toolArgs = [], platform = process.platform) {
|
|
9819
|
+
validateDesktopArgs(toolArgs);
|
|
9820
|
+
const env = { ...process.env };
|
|
9821
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
9822
|
+
delete env.NODE_OPTIONS;
|
|
9823
|
+
const files = [];
|
|
9824
|
+
const appEnv = {};
|
|
9825
|
+
let userData = join11(profileDir, "user-data");
|
|
9826
|
+
if (tool === "codex") {
|
|
9827
|
+
appEnv.CODEX_HOME = join11(profileDir, "codex-home");
|
|
9828
|
+
appEnv.CODEX_ELECTRON_USER_DATA_PATH = userData;
|
|
9829
|
+
}
|
|
9830
|
+
if (tool === "codex" && baseUrl && localKey && model) {
|
|
9831
|
+
const codexHome = join11(profileDir, "codex-home");
|
|
9832
|
+
appEnv.CODEX_HOME = codexHome;
|
|
9833
|
+
appEnv.CODEX_ELECTRON_USER_DATA_PATH = userData;
|
|
9834
|
+
files.push({ path: join11(codexHome, "config.toml"), contents: [
|
|
9835
|
+
`model = ${JSON.stringify(model)}`,
|
|
9836
|
+
'model_provider = "closedrouter"',
|
|
9837
|
+
"",
|
|
9838
|
+
"[model_providers.closedrouter]",
|
|
9839
|
+
'name = "ClosedRouter"',
|
|
9840
|
+
`base_url = ${JSON.stringify(`${baseUrl.replace(/\/+$/, "")}/v1`)}`,
|
|
9841
|
+
'wire_api = "responses"',
|
|
9842
|
+
"requires_openai_auth = true",
|
|
9843
|
+
""
|
|
9844
|
+
].join(`
|
|
9845
|
+
`) });
|
|
9846
|
+
files.push({ path: join11(codexHome, "auth.json"), contents: JSON.stringify({ auth_mode: "apikey", OPENAI_API_KEY: localKey }) });
|
|
9847
|
+
}
|
|
9848
|
+
if (tool === "claude" && baseUrl && localKey && model) {
|
|
9849
|
+
userData = join11(profileDir, "user-data-3p");
|
|
9850
|
+
const id = randomUUID3();
|
|
9851
|
+
const transportModel = model.startsWith("claude-") || model.startsWith("anthropic/claude-") ? model : "claude-sonnet-4-6";
|
|
9852
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}
|
|
9853
|
+
`;
|
|
9854
|
+
files.push({ path: join11(userData, "claude_desktop_config.json"), contents: json({ deploymentMode: "3p" }) });
|
|
9855
|
+
files.push({ path: join11(userData, "configLibrary", "_meta.json"), contents: json({ appliedId: id, entries: [{ id, name: "ClosedRouter" }] }) });
|
|
9856
|
+
files.push({ path: join11(userData, "configLibrary", `${id}.json`), contents: json({
|
|
9857
|
+
inferenceProvider: "gateway",
|
|
9858
|
+
inferenceGatewayBaseUrl: baseUrl,
|
|
9859
|
+
inferenceGatewayApiKey: localKey,
|
|
9860
|
+
inferenceGatewayAuthScheme: "bearer",
|
|
9861
|
+
disableDeploymentModeChooser: true,
|
|
9862
|
+
inferenceModels: [{ name: transportModel, labelOverride: `ClosedRouter: ${model}` }]
|
|
9863
|
+
}) });
|
|
9864
|
+
}
|
|
9865
|
+
Object.assign(env, appEnv);
|
|
9866
|
+
const args = [`--user-data-dir=${userData}`, ...toolArgs];
|
|
9867
|
+
if (platform === "darwin" && executable.endsWith(".app")) {
|
|
9868
|
+
return { command: "/usr/bin/open", args: [
|
|
9869
|
+
"-W",
|
|
9870
|
+
"-n",
|
|
9871
|
+
"-a",
|
|
9872
|
+
executable,
|
|
9873
|
+
...Object.entries(appEnv).flatMap(([key, value]) => ["--env", `${key}=${value}`]),
|
|
9874
|
+
"--args",
|
|
9875
|
+
...args
|
|
9876
|
+
], env, files, profileDir };
|
|
9877
|
+
}
|
|
9878
|
+
return { command: executable, args, env, files, profileDir };
|
|
9879
|
+
}
|
|
9880
|
+
function writeDesktopLaunchPlan(plan) {
|
|
9881
|
+
mkdirSync2(plan.profileDir, { recursive: true, mode: 448 });
|
|
9882
|
+
for (const file of plan.files) {
|
|
9883
|
+
const local = relative(plan.profileDir, file.path);
|
|
9884
|
+
if (!isAbsolute(file.path) || local.startsWith("..") || isAbsolute(local))
|
|
9885
|
+
throw new Error("Desktop configuration must stay inside its launch profile.");
|
|
9886
|
+
mkdirSync2(dirname(file.path), { recursive: true, mode: 448 });
|
|
9887
|
+
writeFileSync3(file.path, file.contents, { mode: 384 });
|
|
9888
|
+
}
|
|
9889
|
+
}
|
|
9890
|
+
function assertClaudeDesktopGatewaySupport(executable) {
|
|
9891
|
+
if (!executable.endsWith(".app"))
|
|
9892
|
+
return;
|
|
9893
|
+
const archive = join11(executable, "Contents", "Resources", "app.asar");
|
|
9894
|
+
const source = readFileSync10(archive);
|
|
9895
|
+
if (!source.includes(Buffer.from("inferenceGatewayBaseUrl"))) {
|
|
9896
|
+
throw new Error("This Claude Desktop build does not include inference gateway support. Update Claude or use cr start claude.");
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9899
|
+
async function runDesktopLaunch(plan) {
|
|
9900
|
+
return await new Promise((resolveExit, reject) => {
|
|
9901
|
+
const controller = new AbortController;
|
|
9902
|
+
const stderrFile = plan.inspectorHook && plan.command !== "/usr/bin/open" ? openSync(plan.inspectorHook.logPath, "a", 384) : undefined;
|
|
9903
|
+
const child = spawn3(plan.command, plan.args, { env: plan.env, stdio: stderrFile === undefined ? "inherit" : ["inherit", "inherit", stderrFile] });
|
|
9904
|
+
if (stderrFile !== undefined)
|
|
9905
|
+
closeSync(stderrFile);
|
|
9906
|
+
const interrupt = () => {
|
|
9907
|
+
controller.abort();
|
|
9908
|
+
child.kill("SIGTERM");
|
|
9909
|
+
};
|
|
9910
|
+
const cleanup = () => {
|
|
9911
|
+
controller.abort();
|
|
9912
|
+
process.off("SIGINT", interrupt);
|
|
9913
|
+
process.off("SIGTERM", interrupt);
|
|
9914
|
+
};
|
|
9915
|
+
process.once("SIGINT", interrupt);
|
|
9916
|
+
process.once("SIGTERM", interrupt);
|
|
9917
|
+
child.once("error", (error) => {
|
|
9918
|
+
cleanup();
|
|
9919
|
+
reject(error);
|
|
9920
|
+
});
|
|
9921
|
+
child.once("exit", (code) => {
|
|
9922
|
+
cleanup();
|
|
9923
|
+
resolveExit(code ?? 1);
|
|
9924
|
+
});
|
|
9925
|
+
if (plan.inspectorHook) {
|
|
9926
|
+
installDesktopInspectorHook(plan.inspectorHook.path, plan.inspectorHook.logPath, controller.signal, plan.inspectorHook).catch((error) => {
|
|
9927
|
+
child.kill("SIGTERM");
|
|
9928
|
+
cleanup();
|
|
9929
|
+
reject(error);
|
|
9930
|
+
});
|
|
9931
|
+
}
|
|
9932
|
+
});
|
|
9933
|
+
}
|
|
9934
|
+
|
|
9935
|
+
// src/commands/cursor-desktop.ts
|
|
9936
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
9937
|
+
import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
|
|
9938
|
+
import { homedir as homedir11 } from "node:os";
|
|
9939
|
+
import { dirname as dirname2, join as join12 } from "node:path";
|
|
9940
|
+
var CURSOR_APPLICATION_STORAGE = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
|
|
9941
|
+
async function writeSqlite(dbPath, sql) {
|
|
9942
|
+
let database;
|
|
9943
|
+
if ("Bun" in globalThis) {
|
|
9944
|
+
const runtimeModule = "bun:sqlite";
|
|
9945
|
+
const sqlite = await import(runtimeModule);
|
|
9946
|
+
database = new sqlite.Database(dbPath);
|
|
9947
|
+
} else {
|
|
9948
|
+
try {
|
|
9949
|
+
const sqlite = await import("node:sqlite");
|
|
9950
|
+
database = new sqlite.DatabaseSync(dbPath);
|
|
9951
|
+
} catch {}
|
|
9952
|
+
}
|
|
9953
|
+
if (database) {
|
|
9954
|
+
try {
|
|
9955
|
+
database.exec(sql);
|
|
9956
|
+
} finally {
|
|
9957
|
+
database.close();
|
|
9958
|
+
}
|
|
9959
|
+
return;
|
|
9960
|
+
}
|
|
9961
|
+
await new Promise((resolve, reject) => {
|
|
9962
|
+
const child = spawn4("sqlite3", [dbPath], { stdio: ["pipe", "ignore", "ignore"] });
|
|
9963
|
+
child.once("error", () => reject(new Error("Cursor desktop configuration requires Bun, Node 22+, or the sqlite3 executable.")));
|
|
9964
|
+
child.once("exit", (code) => code === 0 ? resolve() : reject(new Error("Could not write Cursor desktop configuration.")));
|
|
9965
|
+
child.stdin.on("error", () => {});
|
|
9966
|
+
child.stdin.end(sql);
|
|
9967
|
+
});
|
|
9968
|
+
}
|
|
9969
|
+
function defaultCursorDatabase() {
|
|
9970
|
+
const root = process.platform === "darwin" ? join12(homedir11(), "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA || join12(homedir11(), "AppData", "Roaming") : join12(homedir11(), ".config");
|
|
9971
|
+
return join12(root, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
9972
|
+
}
|
|
9973
|
+
async function configureCursorDesktop(profileDir, baseUrl, apiKey, model, sourceDb = process.env.CLOSEDROUTER_CURSOR_AUTH_DB ?? defaultCursorDatabase()) {
|
|
9974
|
+
const cursorModel = model;
|
|
9975
|
+
const dbPath = join12(profileDir, "user-data", "User", "globalStorage", "state.vscdb");
|
|
9976
|
+
mkdirSync3(dirname2(dbPath), { recursive: true, mode: 448 });
|
|
9977
|
+
const items = new Map;
|
|
9978
|
+
let membershipType;
|
|
9979
|
+
if (existsSync12(sourceDb)) {
|
|
9980
|
+
const auth = await querySqliteKeyValue(sourceDb, "cursorAuth/");
|
|
9981
|
+
if (auth.get("cursorAuth/accessToken") && auth.get("cursorAuth/refreshToken")) {
|
|
9982
|
+
for (const name of ["accessToken", "refreshToken", "cachedEmail", "stripeMembershipAuthId", "stripeMembershipType", "stripeSubscriptionStatus", "membershipType", "cachedSignUpType"]) {
|
|
9983
|
+
const key = `cursorAuth/${name}`;
|
|
9984
|
+
const value = auth.get(key);
|
|
9985
|
+
if (value !== undefined)
|
|
9986
|
+
items.set(key, value);
|
|
9987
|
+
}
|
|
9988
|
+
membershipType = auth.get("cursorAuth/stripeMembershipType") || auth.get("cursorAuth/membershipType");
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9991
|
+
items.set("cursorAuth/openAIKey", apiKey);
|
|
9992
|
+
items.set(CURSOR_APPLICATION_STORAGE, JSON.stringify({
|
|
9993
|
+
useOpenAIKey: true,
|
|
9994
|
+
openAIBaseUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
|
|
9995
|
+
availableAPIKeyModels: [cursorModel],
|
|
9996
|
+
havePerformedSettingsServiceMigration: true,
|
|
9997
|
+
localProviderModelIds: [cursorModel],
|
|
9998
|
+
localProviderAgentModelIds: [cursorModel],
|
|
9999
|
+
...membershipType ? { membershipType } : {},
|
|
10000
|
+
aiSettings: {
|
|
10001
|
+
userAddedModels: [cursorModel],
|
|
10002
|
+
modelOverrideEnabled: [cursorModel],
|
|
10003
|
+
modelOverrideDisabled: [],
|
|
10004
|
+
modelConfig: Object.fromEntries(["cmd-k", "composer", "background-composer", "plan-execution", "spec", "quick-agent"].map((feature) => [feature, { modelName: cursorModel, maxMode: false, selectedModels: [{ modelId: cursorModel, parameters: [] }] }]))
|
|
10005
|
+
}
|
|
10006
|
+
}));
|
|
10007
|
+
items.set("lastSingleModelPreference", JSON.stringify({ composer: cursorModel, "cmd-k": cursorModel }));
|
|
10008
|
+
const quote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
10009
|
+
await writeSqlite(dbPath, [
|
|
10010
|
+
"BEGIN IMMEDIATE;",
|
|
10011
|
+
"CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);",
|
|
10012
|
+
...[...items].map(([key, value]) => `INSERT OR REPLACE INTO ItemTable (key, value) VALUES (${quote(key)}, ${quote(value)});`),
|
|
10013
|
+
"COMMIT;"
|
|
10014
|
+
].join(`
|
|
10015
|
+
`));
|
|
10016
|
+
chmodSync(dbPath, 384);
|
|
10017
|
+
return dbPath;
|
|
10018
|
+
}
|
|
10019
|
+
|
|
10020
|
+
// src/commands/agy-desktop.ts
|
|
10021
|
+
import { createServer as createServer4 } from "node:http";
|
|
10022
|
+
import { StringDecoder } from "node:string_decoder";
|
|
10023
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10024
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
10025
|
+
import { homedir as homedir12 } from "node:os";
|
|
10026
|
+
import { join as join13 } from "node:path";
|
|
10027
|
+
var MODS_CONTROL_URL = "http://127.0.0.1:59998";
|
|
10028
|
+
var MODS_PROXY_URL = "http://127.0.0.1:59997";
|
|
10029
|
+
async function assertAntigravityModsAvailable() {
|
|
10030
|
+
try {
|
|
10031
|
+
const response = await fetch(`${MODS_CONTROL_URL}/action`, {
|
|
10032
|
+
method: "POST",
|
|
10033
|
+
headers: { "content-type": "application/json" },
|
|
10034
|
+
body: JSON.stringify({ action: "getCockpitAccounts" }),
|
|
10035
|
+
signal: AbortSignal.timeout(2000)
|
|
10036
|
+
});
|
|
10037
|
+
if (!response.ok)
|
|
10038
|
+
throw new Error("Not ready");
|
|
10039
|
+
const body = await response.json();
|
|
10040
|
+
if (!body || typeof body !== "object")
|
|
10041
|
+
throw new Error("Unexpected control service");
|
|
10042
|
+
} catch {
|
|
10043
|
+
throw new Error(`Antigravity desktop routing requires the running antigravity-mods daemon at ${MODS_CONTROL_URL}. Start ${join13(homedir12(), "Projects", "antigravity-mods", "bin", "antigravity-ui-daemon.js")} or use --open-only.`);
|
|
10044
|
+
}
|
|
10045
|
+
}
|
|
10046
|
+
function assertAntigravityDesktopHookSupport(executable) {
|
|
10047
|
+
if (!executable.endsWith(".app"))
|
|
10048
|
+
throw new Error("Antigravity desktop routing currently requires a macOS app bundle. Use --open-only on other platforms.");
|
|
10049
|
+
const archive = join13(executable, "Contents", "Resources", "app.asar");
|
|
10050
|
+
const source = readFileSync11(archive);
|
|
10051
|
+
for (const marker of ["--cloud_code_endpoint", "--api_server_url", "startLanguageServer"]) {
|
|
10052
|
+
if (!source.includes(Buffer.from(marker)))
|
|
10053
|
+
throw new Error("This Antigravity build has an unsupported language-server launcher. Use --open-only.");
|
|
10054
|
+
}
|
|
10055
|
+
const framework = join13(executable, "Contents", "Frameworks", "Electron Framework.framework", "Versions", "A", "Electron Framework");
|
|
10056
|
+
if (!existsSync13(framework))
|
|
10057
|
+
throw new Error("Could not verify Antigravity's Electron hook support.");
|
|
10058
|
+
const binary = readFileSync11(framework);
|
|
10059
|
+
const index = binary.indexOf(Buffer.from("dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX"));
|
|
10060
|
+
if (index < 0 || binary[index + 32 + 5] !== 49)
|
|
10061
|
+
throw new Error("This Antigravity build disables launch inspector hooks. Use --open-only.");
|
|
10062
|
+
}
|
|
10063
|
+
async function startAgyDesktopProxy(gatewayUrl, localKey, model, modsUrl = MODS_PROXY_URL) {
|
|
10064
|
+
const capability = randomUUID4();
|
|
10065
|
+
const controllers = new Set;
|
|
10066
|
+
const server = createServer4(async (incoming, outgoing) => {
|
|
10067
|
+
const controller = new AbortController;
|
|
10068
|
+
controllers.add(controller);
|
|
10069
|
+
outgoing.on("close", () => {
|
|
10070
|
+
if (!outgoing.writableFinished)
|
|
10071
|
+
controller.abort();
|
|
10072
|
+
});
|
|
10073
|
+
try {
|
|
10074
|
+
const url = new URL(incoming.url || "/", "http://localhost");
|
|
10075
|
+
const prefix = `/${capability}/`;
|
|
10076
|
+
if (!url.pathname.startsWith(prefix)) {
|
|
10077
|
+
outgoing.writeHead(401).end();
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
const path = url.pathname.slice(prefix.length - 1);
|
|
10081
|
+
const generation = /:(streamGenerateContent|generateContent)$/.exec(path);
|
|
10082
|
+
const chunks = [];
|
|
10083
|
+
let size = 0;
|
|
10084
|
+
for await (const chunk of incoming) {
|
|
10085
|
+
size += chunk.length;
|
|
10086
|
+
if (size > 32 * 1024 * 1024) {
|
|
10087
|
+
outgoing.writeHead(413).end();
|
|
10088
|
+
return;
|
|
10089
|
+
}
|
|
10090
|
+
chunks.push(Buffer.from(chunk));
|
|
10091
|
+
}
|
|
10092
|
+
const raw = Buffer.concat(chunks);
|
|
10093
|
+
const headers = {};
|
|
10094
|
+
let target;
|
|
10095
|
+
let payload = raw;
|
|
10096
|
+
if (generation) {
|
|
10097
|
+
if (incoming.method !== "POST") {
|
|
10098
|
+
outgoing.writeHead(405).end();
|
|
10099
|
+
return;
|
|
10100
|
+
}
|
|
10101
|
+
const body = JSON.parse(raw.toString("utf8"));
|
|
10102
|
+
const request = body.request || body;
|
|
10103
|
+
payload = Buffer.from(JSON.stringify(request));
|
|
10104
|
+
target = `${gatewayUrl}/v1beta/models/${encodeURIComponent(model)}:${generation[1]}${generation[1] === "streamGenerateContent" ? "?alt=sse" : ""}`;
|
|
10105
|
+
headers.authorization = `Bearer ${localKey}`;
|
|
10106
|
+
headers["content-type"] = "application/json";
|
|
10107
|
+
} else {
|
|
10108
|
+
target = `${modsUrl}${path}${url.search}`;
|
|
10109
|
+
for (const name of ["authorization", "content-type", "user-agent"]) {
|
|
10110
|
+
const value = incoming.headers[name];
|
|
10111
|
+
if (typeof value === "string")
|
|
10112
|
+
headers[name] = value;
|
|
10113
|
+
}
|
|
10114
|
+
}
|
|
10115
|
+
const response = await fetch(target, {
|
|
10116
|
+
method: incoming.method,
|
|
10117
|
+
headers,
|
|
10118
|
+
signal: controller.signal,
|
|
10119
|
+
body: incoming.method === "GET" || incoming.method === "HEAD" ? undefined : new Uint8Array(payload)
|
|
10120
|
+
});
|
|
10121
|
+
if (!generation || !response.ok) {
|
|
10122
|
+
outgoing.writeHead(response.status, { "content-type": response.headers.get("content-type") || "application/json" });
|
|
10123
|
+
if (response.body)
|
|
10124
|
+
for await (const chunk of response.body)
|
|
10125
|
+
outgoing.write(chunk);
|
|
10126
|
+
outgoing.end();
|
|
10127
|
+
} else if (generation[1] === "generateContent") {
|
|
10128
|
+
outgoing.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ response: await response.json() }));
|
|
10129
|
+
} else {
|
|
10130
|
+
outgoing.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache" });
|
|
10131
|
+
const decoder = new StringDecoder("utf8");
|
|
10132
|
+
let buffer = "";
|
|
10133
|
+
const emit = (event) => {
|
|
10134
|
+
const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
|
|
10135
|
+
`);
|
|
10136
|
+
if (!data || data === "[DONE]")
|
|
10137
|
+
return;
|
|
10138
|
+
outgoing.write(`data: ${JSON.stringify({ response: JSON.parse(data) })}
|
|
10139
|
+
|
|
10140
|
+
`);
|
|
10141
|
+
};
|
|
10142
|
+
if (response.body)
|
|
10143
|
+
for await (const chunk of response.body) {
|
|
10144
|
+
buffer += decoder.write(Buffer.from(chunk));
|
|
10145
|
+
let match;
|
|
10146
|
+
while (match = /\r?\n\r?\n/.exec(buffer)) {
|
|
10147
|
+
emit(buffer.slice(0, match.index));
|
|
10148
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
buffer += decoder.end();
|
|
10152
|
+
if (buffer.trim())
|
|
10153
|
+
emit(buffer);
|
|
10154
|
+
outgoing.end();
|
|
10155
|
+
}
|
|
10156
|
+
} catch {
|
|
10157
|
+
if (!outgoing.headersSent)
|
|
10158
|
+
outgoing.writeHead(502, { "content-type": "application/json" }).end(JSON.stringify({ error: { message: "Antigravity desktop adapter request failed." } }));
|
|
10159
|
+
else
|
|
10160
|
+
outgoing.destroy();
|
|
10161
|
+
} finally {
|
|
10162
|
+
controllers.delete(controller);
|
|
10163
|
+
}
|
|
10164
|
+
});
|
|
10165
|
+
await new Promise((resolve, reject) => {
|
|
10166
|
+
server.once("error", reject);
|
|
10167
|
+
server.listen(0, "127.0.0.1", () => {
|
|
10168
|
+
server.off("error", reject);
|
|
10169
|
+
resolve();
|
|
10170
|
+
});
|
|
10171
|
+
});
|
|
10172
|
+
const address = server.address();
|
|
10173
|
+
if (!address || typeof address === "string")
|
|
10174
|
+
throw new Error("Could not start Antigravity desktop adapter.");
|
|
10175
|
+
return {
|
|
10176
|
+
baseUrl: `http://127.0.0.1:${address.port}/${capability}`,
|
|
10177
|
+
close: () => new Promise((resolve, reject) => {
|
|
10178
|
+
for (const controller of controllers)
|
|
10179
|
+
controller.abort();
|
|
10180
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
10181
|
+
server.closeAllConnections();
|
|
10182
|
+
})
|
|
10183
|
+
};
|
|
10184
|
+
}
|
|
10185
|
+
function addAntigravityDesktopHook(plan, endpoint) {
|
|
10186
|
+
const path = join13(plan.profileDir, "antigravity-hook.cjs");
|
|
10187
|
+
const contents = `const cp = require('node:child_process');
|
|
10188
|
+
const spawn = cp.spawn;
|
|
10189
|
+
cp.spawn = function(command, args, options) {
|
|
10190
|
+
if (typeof command === 'string' && /(?:^|[\\\\/])language_server(?:\\.exe)?$/.test(command) && Array.isArray(args)) {
|
|
10191
|
+
args = args.slice();
|
|
10192
|
+
for (const [flag, value] of [['--cloud_code_endpoint', ${JSON.stringify(endpoint)}]]) {
|
|
10193
|
+
const index = args.indexOf(flag);
|
|
10194
|
+
if (index < 0) throw new Error('Unsupported Antigravity language-server flags');
|
|
10195
|
+
args[index + 1] = value;
|
|
10196
|
+
}
|
|
10197
|
+
// antigravity-mods discovers its own sessions using the space-separated
|
|
10198
|
+
// csrf flag. Use the equivalent Go flag syntax to keep its periodic global
|
|
10199
|
+
// SetCloudCodeURL pass from replacing this managed session's gateway.
|
|
10200
|
+
const csrf = args.indexOf('--csrf_token');
|
|
10201
|
+
if (csrf >= 0) args.splice(csrf, 2, '--csrf_token=' + args[csrf + 1]);
|
|
10202
|
+
options = { ...options, env: { ...options?.env } };
|
|
10203
|
+
delete options.env.NODE_OPTIONS;
|
|
10204
|
+
}
|
|
10205
|
+
return spawn.call(this, command, args, options);
|
|
10206
|
+
};
|
|
10207
|
+
require('node:fs').writeFileSync(${JSON.stringify(join13(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
|
|
10208
|
+
`;
|
|
10209
|
+
plan.files.push({ path, contents });
|
|
10210
|
+
const logPath = join13(plan.profileDir, "inspector.log");
|
|
10211
|
+
plan.inspectorHook = { path, logPath };
|
|
10212
|
+
plan.args.push("--inspect-brk=127.0.0.1:0");
|
|
10213
|
+
if (plan.command === "/usr/bin/open") {
|
|
10214
|
+
const index = plan.args.indexOf("--args");
|
|
10215
|
+
plan.args.splice(index, 0, "--stderr", logPath);
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
|
|
10219
|
+
// src/commands/claude-desktop.ts
|
|
10220
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
10221
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2 } from "node:fs";
|
|
10222
|
+
import { homedir as homedir13 } from "node:os";
|
|
10223
|
+
import { join as join14 } from "node:path";
|
|
10224
|
+
function findClaudeSharedElectron(sourceApp, home = homedir13()) {
|
|
10225
|
+
const explicit = process.env.CLOSEDROUTER_CLAUDE_ELECTRON || process.env.CLAUDE_DESKTOP_AUTH_ELECTRON;
|
|
10226
|
+
if (explicit && existsSync14(explicit))
|
|
10227
|
+
return explicit;
|
|
10228
|
+
const info = JSON.parse(execFileSync2("/usr/bin/plutil", ["-convert", "json", "-o", "-", join14(sourceApp, "Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist")], { encoding: "utf8" }));
|
|
10229
|
+
const major = info.CFBundleVersion.split(".")[0];
|
|
10230
|
+
const root = join14(home, ".antigravity_cockpit", "electron_runtime");
|
|
10231
|
+
if (!existsSync14(root))
|
|
10232
|
+
return;
|
|
10233
|
+
const versions = readdirSync2(root).filter((version) => version.startsWith(`${major}.`)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
10234
|
+
return versions.map((version) => join14(root, version, `darwin-${process.arch}`, "Electron.app/Contents/MacOS/Electron")).find((path) => existsSync14(path));
|
|
10235
|
+
}
|
|
10236
|
+
function useClaudeSharedElectron(plan, runtime, sourceApp, toolArgs = []) {
|
|
10237
|
+
plan.command = runtime;
|
|
10238
|
+
plan.args = [join14(sourceApp, "Contents/Resources/app.asar"), `--user-data-dir=${join14(plan.profileDir, "user-data-3p")}`, ...toolArgs];
|
|
10239
|
+
}
|
|
10240
|
+
function addClaudeDesktopHook(plan, baseUrl, localKey, sourceApp) {
|
|
10241
|
+
const path = join14(plan.profileDir, "claude-hook.cjs");
|
|
10242
|
+
plan.files.push({ path, contents: `${sourceApp ? `const electron = require('electron');
|
|
10243
|
+
Object.defineProperty(process, 'resourcesPath', {value: ${JSON.stringify(join14(sourceApp, "Contents/Resources"))}});
|
|
10244
|
+
Object.defineProperty(electron.app, 'isPackaged', {get: () => true});
|
|
10245
|
+
electron.app.setPath('userData', ${JSON.stringify(join14(plan.profileDir, "user-data-3p"))});
|
|
10246
|
+
process.env.CLAUDE_USER_DATA_DIR = ${JSON.stringify(join14(plan.profileDir, "user-data-3p"))};
|
|
10247
|
+
` : ""}const cp = require('node:child_process');
|
|
10248
|
+
const spawn = cp.spawn;
|
|
10249
|
+
cp.spawn = function(command, args, options) {
|
|
10250
|
+
if (typeof command === 'string' && /(?:^|[\\\\/])claude(?:\\.exe)?$/.test(command)) {
|
|
10251
|
+
options = { ...options, env: { ...process.env, ...options?.env,
|
|
10252
|
+
ANTHROPIC_BASE_URL: ${JSON.stringify(baseUrl)}, ANTHROPIC_API_KEY: ${JSON.stringify(localKey)} } };
|
|
10253
|
+
delete options.env.ANTHROPIC_AUTH_TOKEN;
|
|
10254
|
+
}
|
|
10255
|
+
return spawn.call(this, command, args, options);
|
|
10256
|
+
};
|
|
10257
|
+
const { autoUpdater } = require('electron');
|
|
10258
|
+
if (autoUpdater) {
|
|
10259
|
+
autoUpdater.checkForUpdates = () => {};
|
|
10260
|
+
autoUpdater.quitAndInstall = () => {};
|
|
10261
|
+
}
|
|
10262
|
+
require('node:fs').writeFileSync(${JSON.stringify(join14(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
|
|
10263
|
+
` });
|
|
10264
|
+
const logPath = join14(plan.profileDir, "inspector.log");
|
|
10265
|
+
plan.inspectorHook = { path, logPath };
|
|
10266
|
+
plan.args.push("--inspect-brk=127.0.0.1:0");
|
|
10267
|
+
if (plan.command === "/usr/bin/open")
|
|
10268
|
+
plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", logPath);
|
|
10269
|
+
}
|
|
10270
|
+
|
|
10271
|
+
// src/commands/cursor-desktop-hook.ts
|
|
10272
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
10273
|
+
import { dirname as dirname3, join as join15 } from "node:path";
|
|
10274
|
+
var FLAGS = /([\w$]+)=\{extensionIsDev:[^{}]{1,1000}localMode:(?:!1|false)\}/;
|
|
10275
|
+
function addCursorDesktopLocalHook(plan, app) {
|
|
10276
|
+
const resources = app.endsWith(".app") ? join15(app, "Contents/Resources/app") : join15(dirname3(app), "resources/app");
|
|
10277
|
+
const source = join15(resources, "out/main.js");
|
|
10278
|
+
const renderer = join15(resources, "out/vs/workbench/workbench.desktop.main.js");
|
|
10279
|
+
const mainSource = readFileSync12(source, "utf8");
|
|
10280
|
+
const rendererSource = readFileSync12(renderer, "utf8");
|
|
10281
|
+
const mainMatch = FLAGS.exec(mainSource);
|
|
10282
|
+
const rendererMatch = FLAGS.exec(rendererSource);
|
|
10283
|
+
if (!mainMatch || !rendererMatch || !rendererSource.includes("runLocalAgentInExtensionHost"))
|
|
10284
|
+
throw new Error("This Cursor Desktop build does not include the supported local agent runtime. Update Cursor or use cr start cursor with Cursor Agent Local.");
|
|
10285
|
+
const nativeKey = mainMatch[1];
|
|
10286
|
+
const authMethod = "isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();return this.scheduleTeamPolicyCheck(),!!(e&&t)}";
|
|
10287
|
+
if (!rendererSource.includes(authMethod))
|
|
10288
|
+
throw new Error("This Cursor Desktop build has an unsupported login controller. Update the ClosedRouter launcher before using local provider mode.");
|
|
10289
|
+
const modifiedRenderer = rendererSource.replace(rendererMatch[0], rendererMatch[0].replace(/localMode:(?:!1|false)/, "localMode:!0")).replace(authMethod, `isAuthenticated(){const e=this.accessToken(),t=this.refreshToken();if(!e&&!t&&${rendererMatch[1]}.localMode&&this.byokProviderKeyStorage?.openAIKey()&&this._reactiveStorageService.applicationUserPersistentStorage.openAIBaseUrl)return true;return this.scheduleTeamPolicyCheck(),!!(e&&t)}`);
|
|
10290
|
+
const patched = join15(plan.profileDir, "cursor-local-workbench.js");
|
|
10291
|
+
const hook = join15(plan.profileDir, "cursor-local-hook.cjs");
|
|
10292
|
+
const marker = join15(plan.profileDir, "main-local-mode-enabled");
|
|
10293
|
+
plan.files.push({ path: patched, contents: modifiedRenderer });
|
|
10294
|
+
plan.files.push({ path: hook, contents: `const {app, session} = require('electron');
|
|
10295
|
+
const fs = require('node:fs');
|
|
10296
|
+
function installProtocolHook() {
|
|
10297
|
+
const protocol = session.defaultSession.protocol;
|
|
10298
|
+
const prototype = Object.getPrototypeOf(protocol);
|
|
10299
|
+
const register = prototype.registerFileProtocol;
|
|
10300
|
+
prototype.registerFileProtocol = function(scheme, handler, ...rest) {
|
|
10301
|
+
if (scheme !== 'vscode-file') return register.call(this, scheme, handler, ...rest);
|
|
10302
|
+
return register.call(this, scheme, (request, callback) => handler(request, result => {
|
|
10303
|
+
if (request.url.includes('workbench.desktop.main.js')) fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-request.json"))}, JSON.stringify({type: typeof result, path: result?.path}), {mode: 0o600});
|
|
10304
|
+
if (result && typeof result === 'object' && result.path === ${JSON.stringify(renderer)}) {
|
|
10305
|
+
result = {...result, path: ${JSON.stringify(patched)}};
|
|
10306
|
+
fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "renderer-local-mode-served"))}, 'ready', {mode: 0o600});
|
|
10307
|
+
}
|
|
10308
|
+
callback(result);
|
|
10309
|
+
}), ...rest);
|
|
10310
|
+
};
|
|
10311
|
+
fs.writeFileSync(${JSON.stringify(join15(plan.profileDir, "protocol-hook-loaded"))}, 'ready', {mode: 0o600});
|
|
10312
|
+
}
|
|
10313
|
+
if (app.isReady()) installProtocolHook(); else app.once('ready', installProtocolHook);
|
|
10314
|
+
require('node:fs').writeFileSync(${JSON.stringify(join15(plan.profileDir, "hook-loaded"))}, 'ready', {mode: 0o600});
|
|
10315
|
+
` });
|
|
10316
|
+
const point = mainSource.slice(0, mainMatch.index + mainMatch[0].length).split(`
|
|
10317
|
+
`);
|
|
10318
|
+
const logPath = join15(plan.profileDir, "inspector.log");
|
|
10319
|
+
plan.inspectorHook = {
|
|
10320
|
+
path: hook,
|
|
10321
|
+
logPath,
|
|
10322
|
+
startupBreakpoint: {
|
|
10323
|
+
urlRegex: source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "$",
|
|
10324
|
+
lineNumber: point.length - 1,
|
|
10325
|
+
columnNumber: point.at(-1).length,
|
|
10326
|
+
condition: `typeof ${nativeKey} === 'object' && (${nativeKey}.localMode = true, process.getBuiltinModule('fs').writeFileSync(${JSON.stringify(marker)}, 'ready', {mode: 0o600}), false)`
|
|
10327
|
+
},
|
|
10328
|
+
readyPath: marker
|
|
10329
|
+
};
|
|
10330
|
+
plan.args.push("--inspect-brk=127.0.0.1:0");
|
|
10331
|
+
if (plan.command === "/usr/bin/open")
|
|
10332
|
+
plan.args.splice(plan.args.indexOf("--args"), 0, "--stderr", logPath);
|
|
10333
|
+
}
|
|
10334
|
+
|
|
8822
10335
|
// src/commands/start.ts
|
|
8823
10336
|
function isGatewayModelAvailable(availableModels, model) {
|
|
8824
10337
|
return availableModels.includes(model);
|
|
@@ -8829,8 +10342,30 @@ var TOOL_OPTIONS = [
|
|
|
8829
10342
|
{ value: "gemini", label: "Gemini CLI", hint: "Google Gemini API" },
|
|
8830
10343
|
{ value: "agy", label: "Antigravity CLI", hint: "Google Gemini API via your ClosedRouter pool" },
|
|
8831
10344
|
{ value: "aider", label: "Aider", hint: "OpenAI-compatible API" },
|
|
8832
|
-
{ value: "opencode", label: "OpenCode", hint: "OpenAI-compatible API" }
|
|
10345
|
+
{ value: "opencode", label: "OpenCode", hint: "OpenAI-compatible API" },
|
|
10346
|
+
{ value: "cursor", label: "Cursor Agent Local", hint: "OpenAI-compatible API; separate preview package required" }
|
|
8833
10347
|
];
|
|
10348
|
+
var DESKTOP_TOOL_OPTIONS = [
|
|
10349
|
+
{ value: "codex-desktop", label: "Codex Desktop", hint: "Responses API with an isolated app profile" },
|
|
10350
|
+
{ value: "antigravity-desktop", label: "Antigravity Desktop", hint: "ClosedRouter routing through your antigravity-mods hook" },
|
|
10351
|
+
{ value: "cursor-desktop", label: "Cursor Desktop", hint: "Automatic API-key setup; requires a public gateway" },
|
|
10352
|
+
{ value: "claude-desktop", label: "Claude Desktop", hint: "Inference gateway with an isolated app profile" }
|
|
10353
|
+
];
|
|
10354
|
+
function getStartToolOptions(desktopOnly = false) {
|
|
10355
|
+
return desktopOnly ? [...DESKTOP_TOOL_OPTIONS] : [...TOOL_OPTIONS, ...DESKTOP_TOOL_OPTIONS];
|
|
10356
|
+
}
|
|
10357
|
+
function resolveStartTarget(value, desktop = false) {
|
|
10358
|
+
const normalized = value.toLowerCase();
|
|
10359
|
+
desktop ||= normalized.endsWith("-desktop");
|
|
10360
|
+
const name = normalized.replace(/-desktop$/, "");
|
|
10361
|
+
const tool = name === "antigravity" ? "agy" : name;
|
|
10362
|
+
if (!TOOL_OPTIONS.some((option) => option.value === tool)) {
|
|
10363
|
+
throw new Error(`Unsupported tool '${value}'. Choose: ${getStartToolOptions().map((option) => option.value).join(", ")}.`);
|
|
10364
|
+
}
|
|
10365
|
+
if (desktop && !isDesktopTool(tool))
|
|
10366
|
+
throw new Error(`Desktop launch is not supported for ${tool}. Choose codex, claude, cursor, or antigravity.`);
|
|
10367
|
+
return { tool, desktop };
|
|
10368
|
+
}
|
|
8834
10369
|
function normalizeServerUrl(serverUrl) {
|
|
8835
10370
|
return serverUrl.replace(/\/+$/, "");
|
|
8836
10371
|
}
|
|
@@ -8843,10 +10378,13 @@ function assertNoManagedCodexArgs(args) {
|
|
|
8843
10378
|
throw new Error(`Remove ${conflicting}; ClosedRouter manages the Codex profile and model. Put other arguments after --.`);
|
|
8844
10379
|
}
|
|
8845
10380
|
}
|
|
8846
|
-
function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir =
|
|
10381
|
+
function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir = homedir14(), executablePath, agyRouting) {
|
|
8847
10382
|
const baseUrl = normalizeServerUrl(serverUrl);
|
|
8848
10383
|
const env = { ...process.env };
|
|
8849
10384
|
const command = executablePath || tool;
|
|
10385
|
+
if (tool === "cursor") {
|
|
10386
|
+
return buildCursorLaunchPlan(baseUrl, apiKey, model, toolArgs, homeDir, executablePath);
|
|
10387
|
+
}
|
|
8850
10388
|
if (tool === "claude") {
|
|
8851
10389
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
8852
10390
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
@@ -8866,9 +10404,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8866
10404
|
}
|
|
8867
10405
|
if (tool === "codex") {
|
|
8868
10406
|
assertNoManagedCodexArgs(toolArgs);
|
|
8869
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
10407
|
+
const codexHome = process.env.CODEX_HOME || join16(homeDir, ".codex");
|
|
8870
10408
|
const profileName = "closedrouter-launch";
|
|
8871
|
-
const configPath =
|
|
10409
|
+
const configPath = join16(codexHome, `${profileName}.config.toml`);
|
|
8872
10410
|
const configLines = [
|
|
8873
10411
|
`model = ${tomlString(model)}`,
|
|
8874
10412
|
`model_provider = ${tomlString(profileName)}`,
|
|
@@ -8894,7 +10432,7 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8894
10432
|
env.GEMINI_API_KEY = apiKey;
|
|
8895
10433
|
env.GEMINI_MODEL = model;
|
|
8896
10434
|
env.GOOGLE_GEMINI_BASE_URL = baseUrl;
|
|
8897
|
-
const configPath =
|
|
10435
|
+
const configPath = join16(homeDir, ".closedrouter", "gemini-launch.settings.json");
|
|
8898
10436
|
env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = configPath;
|
|
8899
10437
|
return {
|
|
8900
10438
|
tool,
|
|
@@ -8957,9 +10495,9 @@ function buildLaunchPlan(tool, serverUrl, apiKey, model, toolArgs = [], homeDir
|
|
|
8957
10495
|
};
|
|
8958
10496
|
}
|
|
8959
10497
|
function writeOwnedConfig(file) {
|
|
8960
|
-
|
|
10498
|
+
mkdirSync4(dirname4(file.path), { recursive: true, mode: 448 });
|
|
8961
10499
|
try {
|
|
8962
|
-
const existing =
|
|
10500
|
+
const existing = readFileSync13(file.path, "utf8");
|
|
8963
10501
|
if (file.path.endsWith(".config.toml") && !existing.includes('name = "ClosedRouter"')) {
|
|
8964
10502
|
throw new Error(`Refusing to replace an existing Codex profile at ${file.path}.`);
|
|
8965
10503
|
}
|
|
@@ -8967,17 +10505,14 @@ function writeOwnedConfig(file) {
|
|
|
8967
10505
|
if (error instanceof Error && error.message.startsWith("Refusing to replace"))
|
|
8968
10506
|
throw error;
|
|
8969
10507
|
}
|
|
8970
|
-
|
|
8971
|
-
}
|
|
8972
|
-
function modelId(model) {
|
|
8973
|
-
return String(model.id || model.name || "");
|
|
10508
|
+
writeFileSync4(file.path, file.contents, { encoding: "utf8", mode: 384 });
|
|
8974
10509
|
}
|
|
8975
10510
|
function modelSupportsHarness(model, tool) {
|
|
8976
10511
|
const parameters = model.supported_parameters;
|
|
8977
10512
|
return (model.status === undefined || model.status === "available") && (model.available_tool_account_count === undefined || model.available_tool_account_count > 0) && (!Array.isArray(parameters) || parameters.includes("tools"));
|
|
8978
10513
|
}
|
|
8979
10514
|
function discoverAgyTransportModel(command, apiKey) {
|
|
8980
|
-
const output =
|
|
10515
|
+
const output = execFileSync3(command, ["models"], {
|
|
8981
10516
|
encoding: "utf8",
|
|
8982
10517
|
timeout: 1e4,
|
|
8983
10518
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -8990,7 +10525,7 @@ function discoverAgyTransportModel(command, apiKey) {
|
|
|
8990
10525
|
}
|
|
8991
10526
|
function assertAgyCustomModel(command, label, apiKey) {
|
|
8992
10527
|
try {
|
|
8993
|
-
const output =
|
|
10528
|
+
const output = execFileSync3(command, ["models"], { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"], env: { ...process.env, GEMINI_API_KEY: apiKey, GOOGLE_GEMINI_BASE_URL: "http://127.0.0.1:1" } });
|
|
8994
10529
|
if (output.split(/\r?\n/).some((line) => line.split("\t")[0] === label))
|
|
8995
10530
|
return;
|
|
8996
10531
|
} catch {
|
|
@@ -8999,28 +10534,24 @@ function assertAgyCustomModel(command, label, apiKey) {
|
|
|
8999
10534
|
throw new Error(`This version of agy did not recognize '${label}'. Update agy to use ClosedRouter's accurate model display.`);
|
|
9000
10535
|
}
|
|
9001
10536
|
function assertAgyGeminiMode(homeDir) {
|
|
9002
|
-
const settingsPath =
|
|
10537
|
+
const settingsPath = join16(homeDir, ".gemini", "antigravity-cli", "settings.json");
|
|
9003
10538
|
try {
|
|
9004
|
-
const settings = JSON.parse(
|
|
10539
|
+
const settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
9005
10540
|
if (settings?.modelProvider === "gemini")
|
|
9006
10541
|
return;
|
|
9007
10542
|
} catch {}
|
|
9008
10543
|
throw new Error(`Set "modelProvider": "gemini" in ${settingsPath} before starting Antigravity through ClosedRouter.`);
|
|
9009
10544
|
}
|
|
9010
|
-
async function selectTool(tool) {
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
if (TOOL_OPTIONS.some((option) => option.value === cleaned))
|
|
9014
|
-
return cleaned;
|
|
9015
|
-
throw new Error(`Unsupported tool '${tool}'. Choose: ${TOOL_OPTIONS.map((option) => option.value).join(", ")}.`);
|
|
9016
|
-
}
|
|
10545
|
+
async function selectTool(tool, desktop = false) {
|
|
10546
|
+
if (tool)
|
|
10547
|
+
return resolveStartTarget(tool, desktop);
|
|
9017
10548
|
const choice = await ve({
|
|
9018
10549
|
message: "Which coding tool should ClosedRouter configure and start?",
|
|
9019
|
-
options:
|
|
10550
|
+
options: getStartToolOptions(desktop)
|
|
9020
10551
|
});
|
|
9021
10552
|
if (pD(choice))
|
|
9022
10553
|
throw new Error("Start cancelled.");
|
|
9023
|
-
return choice;
|
|
10554
|
+
return resolveStartTarget(choice, desktop);
|
|
9024
10555
|
}
|
|
9025
10556
|
async function ensurePlatformKey(tool, client, options) {
|
|
9026
10557
|
const config = loadConfig();
|
|
@@ -9039,20 +10570,56 @@ async function ensurePlatformKey(tool, client, options) {
|
|
|
9039
10570
|
}
|
|
9040
10571
|
async function runTool(plan) {
|
|
9041
10572
|
return await new Promise((resolveExit, rejectExit) => {
|
|
9042
|
-
const child =
|
|
10573
|
+
const child = spawn5(plan.command, plan.args, { env: plan.env, stdio: "inherit" });
|
|
9043
10574
|
child.once("error", rejectExit);
|
|
9044
10575
|
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
9045
10576
|
});
|
|
9046
10577
|
}
|
|
9047
10578
|
async function startCommand(options = {}) {
|
|
9048
|
-
const
|
|
10579
|
+
const groupingOverride = options.groupBy === undefined ? undefined : parseModelGrouping(options.groupBy);
|
|
10580
|
+
const { tool, desktop } = await selectTool(options.tool, options.desktop);
|
|
10581
|
+
if (options.desktopInspector && (!desktop || tool !== "claude"))
|
|
10582
|
+
throw new Error("--desktop-inspector is supported for Claude Desktop only.");
|
|
10583
|
+
if (options.openOnly && !desktop)
|
|
10584
|
+
throw new Error("--open-only requires --desktop.");
|
|
10585
|
+
if (desktop && !isDesktopTool(tool))
|
|
10586
|
+
throw new Error(`Desktop launch is not supported for ${tool}. Choose codex, claude, cursor, or antigravity.`);
|
|
10587
|
+
if (desktop)
|
|
10588
|
+
validateDesktopArgs(options.toolArgs || []);
|
|
10589
|
+
let executablePath = desktop && isDesktopTool(tool) ? resolveDesktopExecutable(tool, options.executablePath) : tool === "cursor" ? resolveCursorLocalExecutable(options.executablePath) : options.executablePath;
|
|
10590
|
+
const sourceApp = executablePath;
|
|
10591
|
+
let sharedClaudeElectron;
|
|
10592
|
+
if (desktop && isDesktopTool(tool) && options.openOnly) {
|
|
10593
|
+
if (options.model || options.key || options.server)
|
|
10594
|
+
throw new Error("Open-only desktop launches do not use --model, --key, or --server.");
|
|
10595
|
+
const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), undefined, undefined, undefined, options.toolArgs);
|
|
10596
|
+
writeDesktopLaunchPlan(plan);
|
|
10597
|
+
M2.warn(`Opening ${tool === "agy" ? "Antigravity" : tool} desktop only. This session is not routed through ClosedRouter.`);
|
|
10598
|
+
process.exitCode = await runDesktopLaunch(plan);
|
|
10599
|
+
return;
|
|
10600
|
+
}
|
|
10601
|
+
if (desktop && tool === "claude") {
|
|
10602
|
+
assertClaudeDesktopGatewaySupport(executablePath);
|
|
10603
|
+
if (options.desktopInspector && process.platform === "darwin" && executablePath.endsWith(".app")) {
|
|
10604
|
+
sharedClaudeElectron = findClaudeSharedElectron(executablePath);
|
|
10605
|
+
if (sharedClaudeElectron)
|
|
10606
|
+
M2.info("Using the shared Electron runtime for Claude's startup inspector. The installed app bundle is reused.");
|
|
10607
|
+
else
|
|
10608
|
+
throw new Error("No compatible shared Electron runtime was found. Set CLOSEDROUTER_CLAUDE_ELECTRON to an installed Electron executable or omit --desktop-inspector to use Claude's native gateway mode.");
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10611
|
+
if (desktop && tool === "agy") {
|
|
10612
|
+
assertAntigravityDesktopHookSupport(executablePath);
|
|
10613
|
+
await assertAntigravityModsAvailable();
|
|
10614
|
+
}
|
|
9049
10615
|
const config = loadConfig();
|
|
9050
10616
|
const serverUrl = normalizeServerUrl(options.server || config.serverUrl);
|
|
9051
10617
|
const client = new ClosedRouterClient({ baseUrl: serverUrl, sessionToken: config.sessionToken });
|
|
9052
10618
|
const apiKey = await ensurePlatformKey(tool, client, options);
|
|
9053
10619
|
client.setApiKey(apiKey);
|
|
9054
10620
|
const models = await client.listGatewayModels({ preferApiKey: true });
|
|
9055
|
-
const
|
|
10621
|
+
const eligibleModels = models.filter((item) => modelSupportsHarness(item, tool));
|
|
10622
|
+
const availableModels = [...new Set(eligibleModels.map(gatewayModelId).filter(Boolean))].sort();
|
|
9056
10623
|
let model = options.model?.trim();
|
|
9057
10624
|
if (!model) {
|
|
9058
10625
|
if (availableModels.length === 0) {
|
|
@@ -9061,27 +10628,70 @@ async function startCommand(options = {}) {
|
|
|
9061
10628
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
9062
10629
|
throw new Error(`Choose a model with --model. Available models: ${availableModels.slice(0, 12).join(", ")}`);
|
|
9063
10630
|
}
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
10631
|
+
model = await selectPoolModel(eligibleModels, {
|
|
10632
|
+
toolLabel: desktop ? `${tool === "agy" ? "Antigravity" : tool} desktop` : TOOL_OPTIONS.find((option) => option.value === tool)?.label,
|
|
10633
|
+
grouping: groupingOverride || config.modelGrouping || "none",
|
|
10634
|
+
favorites: config.favoriteModels,
|
|
10635
|
+
onFavoritesChange: (favoriteModels) => {
|
|
10636
|
+
saveConfig({ favoriteModels });
|
|
10637
|
+
},
|
|
10638
|
+
onGroupingChange: (modelGrouping) => {
|
|
10639
|
+
saveConfig({ modelGrouping });
|
|
10640
|
+
}
|
|
9067
10641
|
});
|
|
9068
|
-
if (pD(choice))
|
|
9069
|
-
throw new Error("Start cancelled.");
|
|
9070
|
-
model = String(choice);
|
|
9071
10642
|
}
|
|
9072
10643
|
if (!isGatewayModelAvailable(availableModels, model)) {
|
|
9073
10644
|
throw new Error(`Model '${model}' is not available for ${tool} with this platform key. Agent clients require a provider that supports gateway function tools. Choose another model from ClosedRouter's model catalog.`);
|
|
9074
10645
|
}
|
|
10646
|
+
if (desktop && tool === "cursor") {
|
|
10647
|
+
const localKey = randomUUID5();
|
|
10648
|
+
const relay = await startLaunchProxy(serverUrl, apiKey, "cursor-desktop", localKey, model);
|
|
10649
|
+
try {
|
|
10650
|
+
const plan = buildDesktopLaunchPlan("cursor", executablePath, createDesktopProfile("cursor"), undefined, undefined, undefined, options.toolArgs);
|
|
10651
|
+
addCursorDesktopLocalHook(plan, executablePath);
|
|
10652
|
+
writeDesktopLaunchPlan(plan);
|
|
10653
|
+
await configureCursorDesktop(plan.profileDir, relay.baseUrl, localKey, model);
|
|
10654
|
+
M2.info(`Starting Cursor desktop local provider mode with ${model}. Model requests use ClosedRouter, without a paid Cursor subscription. Profile: ${plan.profileDir}. Keep cr running until you close this app instance.`);
|
|
10655
|
+
process.exitCode = await runDesktopLaunch(plan);
|
|
10656
|
+
} finally {
|
|
10657
|
+
await relay.close();
|
|
10658
|
+
}
|
|
10659
|
+
return;
|
|
10660
|
+
}
|
|
10661
|
+
if (desktop && (tool === "codex" || tool === "claude" || tool === "agy")) {
|
|
10662
|
+
const localKey = randomUUID5();
|
|
10663
|
+
const relay = await startLaunchProxy(serverUrl, apiKey, `${tool}-desktop`, localKey, tool === "claude" ? model : undefined);
|
|
10664
|
+
let agyDesktopProxy;
|
|
10665
|
+
try {
|
|
10666
|
+
const plan = buildDesktopLaunchPlan(tool, executablePath, createDesktopProfile(tool), relay.baseUrl, localKey, model, options.toolArgs);
|
|
10667
|
+
if (options.desktopInspector && tool === "claude" && executablePath.endsWith(".app") && process.platform === "darwin") {
|
|
10668
|
+
if (sharedClaudeElectron)
|
|
10669
|
+
useClaudeSharedElectron(plan, sharedClaudeElectron, sourceApp, options.toolArgs);
|
|
10670
|
+
addClaudeDesktopHook(plan, relay.baseUrl, localKey, sharedClaudeElectron ? sourceApp : undefined);
|
|
10671
|
+
}
|
|
10672
|
+
if (tool === "agy") {
|
|
10673
|
+
agyDesktopProxy = await startAgyDesktopProxy(relay.baseUrl, localKey, model);
|
|
10674
|
+
addAntigravityDesktopHook(plan, agyDesktopProxy.baseUrl);
|
|
10675
|
+
}
|
|
10676
|
+
writeDesktopLaunchPlan(plan);
|
|
10677
|
+
M2.info(`Starting ${tool} desktop with ${model} through ${serverUrl}. Profile: ${plan.profileDir}. Keep cr running until you close this app instance.`);
|
|
10678
|
+
process.exitCode = await runDesktopLaunch(plan);
|
|
10679
|
+
} finally {
|
|
10680
|
+
await agyDesktopProxy?.close();
|
|
10681
|
+
await relay.close();
|
|
10682
|
+
}
|
|
10683
|
+
return;
|
|
10684
|
+
}
|
|
9075
10685
|
if (tool === "agy")
|
|
9076
|
-
assertAgyGeminiMode(
|
|
9077
|
-
const recognizedModel = tool === "agy" ? registerAgyLaunchModel(
|
|
10686
|
+
assertAgyGeminiMode(homedir14());
|
|
10687
|
+
const recognizedModel = tool === "agy" ? registerAgyLaunchModel(homedir14(), model, discoverAgyTransportModel(options.executablePath || "agy", apiKey)) : undefined;
|
|
9078
10688
|
if (recognizedModel)
|
|
9079
10689
|
assertAgyCustomModel(options.executablePath || "agy", recognizedModel, apiKey);
|
|
9080
10690
|
const relay = await startLaunchProxy(serverUrl, apiKey, tool);
|
|
9081
10691
|
let agyProxy;
|
|
9082
10692
|
try {
|
|
9083
10693
|
agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model) : undefined;
|
|
9084
|
-
const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs,
|
|
10694
|
+
const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir14(), executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
|
|
9085
10695
|
if (plan.configFile)
|
|
9086
10696
|
writeOwnedConfig(plan.configFile);
|
|
9087
10697
|
M2.info(`${import_picocolors12.default.green("Starting")} ${import_picocolors12.default.bold(TOOL_OPTIONS.find((option) => option.value === tool)?.label || tool)} with ${import_picocolors12.default.cyan(model)} through ${import_picocolors12.default.dim(serverUrl)}`);
|
|
@@ -9138,7 +10748,7 @@ process.on("warning", (warning) => {
|
|
|
9138
10748
|
console.warn(warning.name, warning.message);
|
|
9139
10749
|
});
|
|
9140
10750
|
var program2 = new Command2;
|
|
9141
|
-
var cliVersion = JSON.parse(
|
|
10751
|
+
var cliVersion = JSON.parse(readFileSync14(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
9142
10752
|
program2.name("closedrouter").description("ClosedRouter CLI: Import local AI subscriptions & API keys into your shared token pool").version(cliVersion).option("-s, --server <url>", "ClosedRouter server base URL (default: http://localhost:8000)").action(async (options) => {
|
|
9143
10753
|
await onboardCommand({ server: options.server });
|
|
9144
10754
|
});
|
|
@@ -9186,14 +10796,21 @@ program2.command("gateway").alias("daemon").alias("bridge").description("Start t
|
|
|
9186
10796
|
claudePath: options.claudePath
|
|
9187
10797
|
});
|
|
9188
10798
|
});
|
|
9189
|
-
program2.command("
|
|
10799
|
+
program2.command("settings").description("Configure the CLI model selector").option("--model-grouping <grouping>", "Group models by model-provider, account-provider, or none").action(async (options) => {
|
|
10800
|
+
await settingsCommand({ modelGrouping: options.modelGrouping });
|
|
10801
|
+
});
|
|
10802
|
+
program2.command("start [tool] [tool-args...]").alias("launch").description("Configure and start a coding CLI or desktop app through ClosedRouter").option("--desktop", "Launch the desktop app for codex, claude, cursor, or antigravity").option("--open-only", "Open an isolated desktop app without gateway configuration").option("--desktop-inspector", "Enable Claude's startup hook using an installed shared Electron runtime").option("-s, --server <url>", "ClosedRouter server base URL").option("-k, --key <key>", "Platform API key; otherwise use the saved CLI key").option("-m, --model <model>", "Gateway model to use").option("--group-by <grouping>", "Override model grouping for this launch: model-provider, account-provider, or none").option("--path <path>", "Path to the installed CLI, desktop executable, or macOS app bundle").allowUnknownOption().action(async (tool, toolArgs, options) => {
|
|
9190
10803
|
await startCommand({
|
|
9191
10804
|
tool,
|
|
9192
10805
|
toolArgs: toolArgs || [],
|
|
9193
10806
|
server: options.server || program2.opts().server,
|
|
9194
10807
|
key: options.key,
|
|
9195
10808
|
model: options.model,
|
|
9196
|
-
|
|
10809
|
+
groupBy: options.groupBy,
|
|
10810
|
+
executablePath: options.path,
|
|
10811
|
+
desktop: options.desktop,
|
|
10812
|
+
openOnly: options.openOnly,
|
|
10813
|
+
desktopInspector: options.desktopInspector
|
|
9197
10814
|
});
|
|
9198
10815
|
});
|
|
9199
10816
|
program2.parseAsync(process.argv).catch((error) => {
|