mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
881 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 613a8d1beb | |||
| 708557880b | |||
| 0f5d4ae648 | |||
| ce72020750 | |||
| c43d606f8e | |||
| 1007630347 | |||
| 9e8274d2da | |||
| 74aa735e6a | |||
| 8030a6c187 | |||
| e5aa5161f2 | |||
| abb1ee6278 | |||
| c4003579bb | |||
| 0d9c534184 | |||
| 5773d43cbf | |||
| e0e9414cbd | |||
| 44edb639c2 | |||
| fbd52ca2f4 | |||
| 8015ff7ca5 | |||
| ec9584177f | |||
| 061efc6cf2 | |||
| fe374aea46 | |||
| 46edc98f10 | |||
| fdeb2748e1 | |||
| 59e6967b8f | |||
| 0c619cb59c | |||
| 0cf90109dc | |||
| 812668ae2f | |||
| a5c35bf182 | |||
| bdd5a80886 | |||
| fd65d29dcc | |||
| d3caac5270 | |||
| fe7ca3421e | |||
| cc95197d72 | |||
| 9067218b74 | |||
| 42a0453945 | |||
| 4d9eb6c320 | |||
| c060c436b6 | |||
| 45adfedd64 | |||
| 0bced8ec96 | |||
| 8d9b9719be | |||
| c7e084c32c | |||
| 6f2f759fbb | |||
| cddab63808 | |||
| 12583b18f0 | |||
| df386bd651 | |||
| 25b12ed754 | |||
| 023e1c711e | |||
| 52f7ba7d4d | |||
| 19fce2bc6f | |||
| 4bae84c8b0 | |||
| f240bba8e7 | |||
| bcee247988 | |||
| 64c504212a | |||
| 6592d80460 | |||
| d821650b48 | |||
| 6c9f12bb2d | |||
| fca89e5e4d | |||
| 1eff01b6a5 | |||
| e93b1f3a7f | |||
| 8874d4a42f | |||
| c933504d9c | |||
| 2d0d3d596e | |||
| 3bd98ea055 | |||
| 7e997cfba4 | |||
| 5d6f2a1524 | |||
| b1cb71856e | |||
| 518264fcd9 | |||
| 7235c9c9b8 | |||
| 274033cd52 | |||
| 9b369ee815 | |||
| 721ff5121e | |||
| cfbf5d1c6f | |||
| 02cb7e7b71 | |||
| 942630eb4a | |||
| ce66b191d1 | |||
| 6f1f5944ce | |||
| 1c49b2ed67 | |||
| 3c6be604ec | |||
| ddc02c2893 | |||
| 2703eff2e2 | |||
| 38e4540119 | |||
| 4100fcbd17 | |||
| 83cb0f60ec | |||
| effd96755e | |||
| 5801cce1b5 | |||
| 77e6c0d329 | |||
| fed716ada5 | |||
| 426d92e352 | |||
| d0412a213b | |||
| 16aa67086b | |||
| 7cea32ee08 | |||
| 128d10d9e9 | |||
| 5ef72e1101 | |||
| d8060dc9ad | |||
| f71fb18d3d | |||
| 5654dd2aad | |||
| 64dde0cb15 | |||
| a658e43eeb | |||
| ca8a42c973 | |||
| 8f5f75db12 | |||
| 4ff0d07b1d | |||
| 2a571b3cee | |||
| 0fffcdfe46 | |||
| 2ba2ee52e8 | |||
| 56d818fc34 | |||
| 9c8da69196 | |||
| a78018697c | |||
| e45b6ef1de | |||
| b616543ac2 | |||
| 2bd3d9a696 | |||
| fa15dbc5ec | |||
| 312e5c7a7c | |||
| 049502fac6 | |||
| cc2915be16 | |||
| ce061bf661 | |||
| 3b8790e034 | |||
| a4f3cedcdf | |||
| 1c9a2eb239 | |||
| 4fb417d3b5 | |||
| 11030c627b | |||
| c104098a66 | |||
| 49ee3ba85a | |||
| 4fc538378d | |||
| d28b5ad2f4 | |||
| 6589a66822 | |||
| 5cf9abe743 | |||
| 903d81819d | |||
| 472f9e64a6 | |||
| c04fa9e253 | |||
| 3a78fb1f42 | |||
| 85ce6a5f95 | |||
| 5217e6c1af | |||
| 3a7f617098 | |||
| d9150413cb | |||
| bcbc1dba22 | |||
| ce3235e115 | |||
| a9a2a597d5 | |||
| 3753601f87 | |||
| fb4bab8a66 | |||
| b3526f6ce9 | |||
| f220f02a2f | |||
| 235a86fb60 | |||
| 67b9c9c027 | |||
| 2f11c9f7ed | |||
| e1c1193f3e | |||
| 29250a0efb | |||
| c6e6bdf59f | |||
| d80e1199ca | |||
| 10ea59066f | |||
| 79d6b10d7c | |||
| 6e78f36a0f | |||
| 16866e1180 | |||
| 6d130e5deb | |||
| e30d8173c1 | |||
| 7a79f3a5ea | |||
| b8ca71d309 | |||
| 6849f96825 | |||
| 00c3248295 | |||
| 818b56dbd0 | |||
| 29b5b24787 | |||
| 11363170ca | |||
| ba9e4b67ed | |||
| bd1029b19f | |||
| 43b51f09d0 | |||
| c61ab51886 | |||
| d373c562f2 | |||
| 5fa5d876fc | |||
| 805af011c9 | |||
| 480aa8b23b | |||
| d62442bb5d | |||
| 8602937a37 | |||
| 77da433e0a | |||
| ad79f3e0cf | |||
| 6c2dfd2f52 | |||
| 9a8b54fe62 | |||
| dcdbdb218f | |||
| 5e49029e70 | |||
| 19abadaf27 | |||
| 6fea0178eb | |||
| ebe9dcf27e | |||
| 57efec4429 | |||
| e22144806f | |||
| 27fa297a42 | |||
| b1cd25de3d | |||
| 780bbb0f3b | |||
| 817cb076a8 | |||
| 347526e9fd | |||
| 092bc674a5 | |||
| b24a4e897e | |||
| 3afa622eab | |||
| d01cb7f013 | |||
| 28b03595bf | |||
| 32684e70e6 | |||
| b2baddcd37 | |||
| dbd48d423d | |||
| e7cc8259b5 | |||
| a9ccb0804f | |||
| ebe6087e8f | |||
| dc978cb889 | |||
| 8cbc43fbb0 | |||
| 82359c4b1b | |||
| 7f3e51453b | |||
| cbdb2d9825 | |||
| 96bde05f6e | |||
| dcb8ed8eb0 | |||
| aab82cc1a7 | |||
| cd1d1e81a6 | |||
| cff441909a | |||
| 3615d5aab1 | |||
| 11d9e82eaf | |||
| eadda11ec9 | |||
| f73a56c223 | |||
| c0acf5c43f | |||
| 4d585464f3 | |||
| f0cb17a812 | |||
| 357a74714a | |||
| ffea6c7974 | |||
| 8e9550d90d | |||
| 9b7b6cb30f | |||
| cc68afb2de | |||
| 11c33d52a5 | |||
| 2a487ea990 | |||
| 9c05d4e2fd | |||
| 0745162eab | |||
| 21d055be19 | |||
| d19f7bc77c | |||
| 8694da9309 | |||
| a0fc27e424 | |||
| 35deef6175 | |||
| 36f8b7e47d | |||
| f4337dff3c | |||
| ba8c920639 | |||
| 5bb7b23440 | |||
| dc7d665e94 | |||
| 40d5ea1cf1 | |||
| 6e47ae769e | |||
| 9ca4be62bd | |||
| fed221e0b0 | |||
| 75308ea47d | |||
| daa3116f4b | |||
| 9e7f7bf8e4 | |||
| 4d43d584fe | |||
| 3052a79b32 | |||
| 13b3117ca9 | |||
| 83bb216486 | |||
| fc46cef5fd | |||
| 799996db76 | |||
| df75bfe07c | |||
| c818c9dcb6 | |||
| c36ab3f935 | |||
| e3c983c21f | |||
| a196569f51 | |||
| 19da27e1cb | |||
| 4a737493ac | |||
| ec301f6a2c | |||
| ac7a885ae9 | |||
| 7f2b5ee8c2 | |||
| 15784aa036 | |||
| edbc02855d | |||
| a43d3e0e1e | |||
| ae25278eda | |||
| 6869186fc6 | |||
| f8c6742e54 | |||
| 014dbd34c4 | |||
| 21ae91b4f2 | |||
| bb3f14119b | |||
| 6f165e23de | |||
| cef0c8ac84 | |||
| dd8bb44d1d | |||
| 30868f52ea | |||
| 9c88235121 | |||
| 4e14f79511 | |||
| e0396b809a | |||
| 319498e2fd | |||
| 2ba9aa2196 | |||
| 114eeb21dc | |||
| 1cf8123bc6 | |||
| db6a038829 | |||
| 6ff833a22b | |||
| 5c401673b2 | |||
| e8ce5df414 | |||
| b8799be3c8 | |||
| 7ded0ec9e9 | |||
| f5d0371efe | |||
| 22e64cac67 | |||
| 2a1305f231 | |||
| e691e8f274 | |||
| cc6dd5321c | |||
| 2c17e3a4db | |||
| 98f5e6e713 | |||
| 474e311f6f | |||
| 626a488fb8 | |||
| a300a6cc7a | |||
| fe594693a4 | |||
| 98e091796b | |||
| 3c4b4d5faf | |||
| b6ff1b18c7 | |||
| 9c9bc09f52 | |||
| d6e06c8950 | |||
| 844fb71938 | |||
| fbb7b5b1bf | |||
| 95280ebec9 | |||
| fea9a0bd4c | |||
| 30c4fcb1a5 | |||
| 193c169ca5 | |||
| cee04f2924 | |||
| 1219691c11 | |||
| b2cc40f09c | |||
| 0b2e65f16d | |||
| 1ea01fdad0 | |||
| f8aa4a3be0 | |||
| 293bb422fa | |||
| 54a78c9224 | |||
| 9b30ee2db2 | |||
| ba1ec62caf | |||
| 72ec05d0be | |||
| 0b702704ae | |||
| 3480cef52e | |||
| dcfe4b0d51 | |||
| b2e3dc87ea | |||
| 233fc5b910 | |||
| 2dffdfff4a | |||
| a4ab1408eb | |||
| e41843eaf7 | |||
| bf979413f9 | |||
| 38b0cdc149 | |||
| 344ccc647b | |||
| b9b854bf9f | |||
| d9c18381a6 | |||
| 63a175b50d | |||
| 889f979c0b | |||
| 2abc4507b2 | |||
| aa3c99a3c0 | |||
| d49d217e9d | |||
| 043a5c7c0d | |||
| 901d1171a6 | |||
| 518503b29b | |||
| c235ba1bef | |||
| 754a1fb712 | |||
| acca2e92dc | |||
| 9d178e0944 | |||
| 7c8cf6ca5b | |||
| 89afac3d9d | |||
| b4c60e1b21 | |||
| efd8024430 | |||
| 2f05676e04 | |||
| 5013e8a8ec | |||
| 6e7c9eb820 | |||
| 8555de8189 | |||
| f5c3d352a1 | |||
| e117397d0f | |||
| 1fbc13a1b4 | |||
| 6409aceb1a | |||
| 837cc92586 | |||
| ca77b8f8e9 | |||
| 25547e9337 | |||
| 12f3d1f505 | |||
| 8e182c7782 | |||
| 8a797ed9a1 | |||
| 25ecf0af6b | |||
| 576480b5dc | |||
| fdb4b7c4a5 | |||
| 726ae6f541 | |||
| 773078e81f | |||
| 811954880e | |||
| 465c83cf82 | |||
| bb9b81aa37 | |||
| a20446fcc8 | |||
| 292c2aa458 | |||
| 52bb088753 | |||
| b8f8f5d3a8 | |||
| 8df3ef10fc | |||
| 301ab36159 | |||
| 03544a26cd | |||
| b4147c8d08 | |||
| 6f7d63e9ce | |||
| 07f1c8c0ac | |||
| 2d0a757eb2 | |||
| 75d141b574 | |||
| 39c88f9afb | |||
| 0df2bb0f3b | |||
| f6a3615f59 | |||
| edd480f56b | |||
| 2740d398fa | |||
| f33b17e8ac | |||
| 22a4a9df8b | |||
| 84afd2bef8 | |||
| ca2411d332 | |||
| 6b852774e1 | |||
| f14784d531 | |||
| 6a5e329427 | |||
| 4b65b1e053 | |||
| d431a0e4b4 | |||
| 5720883d5d | |||
| 007b57f078 | |||
| fb07c2070c | |||
| 25dc6f09bc | |||
| b70e2700ef | |||
| 1aed6b1d8b | |||
| c1f607d206 | |||
| 2c819f290f | |||
| 6e9f10ad3f | |||
| 1251a870cb | |||
| 67047fa766 | |||
| a366128a93 | |||
| 9f708e748a | |||
| 7bc26dafae | |||
| ce89bcb8e2 | |||
| c2b1974ddd | |||
| ca6150d6f0 | |||
| 825ab2e38d | |||
| 755cd561ec | |||
| 6312c55d55 | |||
| a9dc0fae3d | |||
| 7749d8e85f | |||
| 28112fbd12 | |||
| 387220f368 | |||
| adb7cb1037 | |||
| c06af70ab0 | |||
| 40dc2fa3c1 | |||
| df7dd06a0f | |||
| 57d5c095d8 | |||
| 13ac849db5 | |||
| 8694c5b68f | |||
| 0a7d02c87c | |||
| e77867ef05 | |||
| fb224d8974 | |||
| 101566131d | |||
| 8433e8b433 | |||
| 379600b5ab | |||
| 7a503de606 | |||
| 2ad1eb56d3 | |||
| a43f767abb | |||
| 0ee3b87289 | |||
| 3c9f3c5786 | |||
| ca75ac6681 | |||
| d1f597b5b5 | |||
| 8299fb3e2b | |||
| 4f7f90133d | |||
| b205e104f6 | |||
| 252e2f98e6 | |||
| e2afdc1202 | |||
| a08e4c9651 | |||
| 7ccab8d272 | |||
| fc57eb3b8e | |||
| 9179bafd54 | |||
| 2df8eda8a3 | |||
| bd32252a7e | |||
| 1717d636a2 | |||
| 8e016b4703 | |||
| b89d48a2a4 | |||
| 33312bfd1b | |||
| 3f1ce36418 | |||
| 0e13279545 | |||
| 5f03d892c0 | |||
| bdabb102fe | |||
| a79a6594b0 | |||
| a3d282a4c2 | |||
| db24f89313 | |||
| 31cb0bfa4f | |||
| af9fdf0a1c | |||
| be88cd5cb9 | |||
| b4cc7d13b6 | |||
| 0ba013f8de | |||
| 0956b15c52 | |||
| 61150f6391 | |||
| 7409dcc6bd | |||
| 2829943ad1 | |||
| c4311dda31 | |||
| ad05a46d74 | |||
| a6cadba814 | |||
| a3bc5d35b0 | |||
| 1409a0715c | |||
| e98c291866 | |||
| e709dc34fb | |||
| 9293cddb3a | |||
| 68b3448b09 | |||
| 80f2b13a55 | |||
| 7d91d3b1ed | |||
| a6464062b7 | |||
| fd01dc9c89 | |||
| d10fb88b66 | |||
| 6b68b1020e | |||
| 85bb9007ba | |||
| 9bef88e3b0 | |||
| f98053c34e | |||
| 36007aecf4 | |||
| 4de44bbbef | |||
| 9d03d4419e | |||
| 7ab1c1c74a | |||
| 3f459819ba | |||
| 1986a6e817 | |||
| dfe1325fca | |||
| c1686c6ddc | |||
| 79b6ce5db4 | |||
| 0c816eb4b1 | |||
| e318e173d8 | |||
| b314781a1a | |||
| 8396d6b016 | |||
| 43e20874f4 | |||
| c444e971b0 | |||
| 430bde9e9b | |||
| 05b82a6a30 | |||
| 6cd02c05c2 | |||
| b3a7513765 | |||
| f8738c9002 | |||
| b460db15d7 | |||
| ff4779ca11 | |||
| 146ff8ad85 | |||
| 0d0ec7dc46 | |||
| 1ea6e6cd4b | |||
| 96061222d2 | |||
| 3b9155714d | |||
| 7371db5cc6 | |||
| b09b7d28b8 | |||
| 31ed4602e1 | |||
| 6a76346734 | |||
| 78b3000031 | |||
| 4c4860fb24 | |||
| 5242a1c6b4 | |||
| 075f876e6f | |||
| a849812e9f | |||
| d99dde6306 | |||
| becf57ee6a | |||
| f33aec1139 | |||
| 1571933096 | |||
| 160928a9a9 | |||
| d297c29f22 | |||
| 0b498dd448 | |||
| cec9c6122a | |||
| 51e310c9ce | |||
| 478156456e | |||
| 6252412d94 | |||
| c2609cbf04 | |||
| 2115df57bf | |||
| 29ec07700c | |||
| bcae852d28 | |||
| 16ddf5f559 | |||
| 8c79c58c4d | |||
| 97ed9ba624 | |||
| a6b6395c8a | |||
| 21f8027ef7 | |||
| a5aa72bd7d | |||
| 563177c6ac | |||
| 4eae8ec037 | |||
| 08895c396e | |||
| 4e451a4b0f | |||
| 163290bcf0 | |||
| c68c33d4fe | |||
| 3615d8e226 | |||
| 2283979199 | |||
| 33f7f593ee | |||
| 461e7345b3 | |||
| 6bd91c68e8 | |||
| ff55a40749 | |||
| 8b56d77ea1 | |||
| dd3aa96730 | |||
| 8b56d1712f | |||
| 3c24d22d42 | |||
| 4c70ea28d2 | |||
| 5ba68a28c0 | |||
| bce4def2db | |||
| 3544ea0244 | |||
| 6434918794 | |||
| 5984d917dc | |||
| c2a97a7a6c | |||
| a083c88e87 | |||
| ce3b0988c4 | |||
| e8a194a2bb | |||
| 8aa8798e07 | |||
| 6d4629b566 | |||
| a9d399699e | |||
| bc805b3001 | |||
| 668d77bb4e | |||
| 5c2e06f353 | |||
| a499fe2b17 | |||
| 451650b584 | |||
| 1b76bec0e2 | |||
| 96f4da1e1d | |||
| 96a0dd6b04 | |||
| 2dd1f2d453 | |||
| 510f01674a | |||
| e3134a2a99 | |||
| 8805104b8d | |||
| fc155e9fc5 | |||
| 3aaac0098e | |||
| a12333310f | |||
| 247284b9af | |||
| e0305e47f3 | |||
| 76a0f0f619 | |||
| 560baae15d | |||
| 5518ecaefe | |||
| 924ba97055 | |||
| b80f52f8ad | |||
| feb275d08b | |||
| fbcbd24063 | |||
| 3250b814ce | |||
| 0e9d9282c6 | |||
| b315a70773 | |||
| cedff6fb89 | |||
| 87cd9446d8 | |||
| f4ce240a2e | |||
| 320527a3e4 | |||
| 19271fca2d | |||
| feeebbe7d4 | |||
| f384675c01 | |||
| ec3ab4a00c | |||
| e4ac936eb9 | |||
| 79e23b7eb9 | |||
| 92e80b4660 | |||
| ce63ca4d7a | |||
| fef7981942 | |||
| ffe0314c47 | |||
| 375444a149 | |||
| 65c15afe9f | |||
| 8f57a2a462 | |||
| 53e9cac383 | |||
| fe0c182747 | |||
| 29b1060c67 | |||
| dddfcbf0d8 | |||
| 62e1335388 | |||
| 908e28175f | |||
| 3398fd7719 | |||
| 9bddf7f3ef | |||
| 8ba374fefa | |||
| 3ef0aaf768 | |||
| d7701dbfb6 | |||
| c49bf0b402 | |||
| cee9610d26 | |||
| 38adc13295 | |||
| 4fe14abb8c | |||
| 9052e8a1ba | |||
| de78dedceb | |||
| 6f508d574e | |||
| 61dfae31e7 | |||
| ac6aa43e3b | |||
| ea89925042 | |||
| 12cbfe5b64 | |||
| d7b7be1909 | |||
| a740d2c667 | |||
| 588261076a | |||
| 639e27c3ce | |||
| 1124ae17b4 | |||
| 9db5890ce5 | |||
| 293877cb7e | |||
| c480006554 | |||
| 6aa8e894b1 | |||
| 00bb9836a6 | |||
| 71f9189607 | |||
| a3f7ea2555 | |||
| d3df8e1180 | |||
| df147b65fd | |||
| 6015084fa2 | |||
| 65ba1f6c13 | |||
| d37e5af57d | |||
| d71b827d8c | |||
| 504ca3d3d8 | |||
| a8c74c04de | |||
| f6b4f54216 | |||
| fc0e3c65b3 | |||
| 23b8ed788e | |||
| 3bd890f46b | |||
| 9fbeafb63e | |||
| 91bd295209 | |||
| d4bf70be06 | |||
| ae8904c4ff | |||
| 9209c04370 | |||
| 379e7f3f20 | |||
| 366d11e1f8 | |||
| 58836e75f0 | |||
| 0acac216ae | |||
| 276d162044 | |||
| 1b0ed983c5 | |||
| 2e8d690ab1 | |||
| 1ff8d289af | |||
| d54ffbda1c | |||
| c00058ed7a | |||
| 2c2fc3499b | |||
| ea3c6c3481 | |||
| 9b68b7195a | |||
| 7739cc53b4 | |||
| 3fa78a8b01 | |||
| e57d0c2fee | |||
| 2a4f2bf527 | |||
| aa07f38b07 | |||
| 9d1f17d836 | |||
| b420952e59 | |||
| bb9e445257 | |||
| 528fb1d404 | |||
| c8d9f7aa89 | |||
| cd7ec93cdf | |||
| 796b652d2b | |||
| 4d74849c1a | |||
| 937a7c48a5 | |||
| 704eb00de4 | |||
| bad4599bf9 | |||
| 892fd85ba7 | |||
| 0eaa47d857 | |||
| faca24d487 | |||
| c103202ad5 | |||
| ce78a4265d | |||
| c4a2353ac3 | |||
| 576efed196 | |||
| dfc0075f90 | |||
| acd15dcc8a | |||
| 139c4fd555 | |||
| e0f3df8252 | |||
| 9cd2e3a1c3 | |||
| f584f80219 | |||
| 45eac589f8 | |||
| fab1768826 | |||
| 51fc10e407 | |||
| 7a1c8465f5 | |||
| 5290e9ca7e | |||
| c361c2953f | |||
| ccb7669736 | |||
| f25f1485d5 | |||
| 55ecb06748 | |||
| dc6991e5a8 | |||
| 738b3065dc | |||
| 26cc537cb1 | |||
| ede354b0e6 | |||
| 61eabfc60c | |||
| 2789b770aa | |||
| 8718b98ee1 | |||
| c8b2f987f9 | |||
| 52b55b826f | |||
| e8c20235b8 | |||
| 17701628bd | |||
| 0efc6163f1 | |||
| 1e191ba815 | |||
| f19d863689 | |||
| 4a1ef327ca | |||
| 025a6392ce | |||
| e578c442be | |||
| 0cecb1bff2 | |||
| 5d8971c1ed | |||
| a9b62d67df | |||
| 3525e61906 | |||
| 059e6c46db | |||
| 5cf195e0af | |||
| 244d1debe4 | |||
| 35734b42fe | |||
| a3128e32c5 | |||
| 5f8a72bfc4 | |||
| 418a1cf5f3 | |||
| 60ebd074ac | |||
| 216dd363e8 | |||
| 141f33d24b | |||
| c4d8a8183e | |||
| 58244eb687 | |||
| e9071b0a80 | |||
| c68907ece2 | |||
| af3998c8a6 | |||
| fad2618757 | |||
| 21e01dbe04 | |||
| 3beadeebff | |||
| dcee1c3642 | |||
| 00d1a7e090 | |||
| bbb56c2a88 | |||
| 5186c6964b | |||
| 79c66e353f | |||
| 41f5e8a861 | |||
| c5b67927af | |||
| 301ecb185e | |||
| 151df05eeb | |||
| 55adcdfd07 | |||
| daaa2e5911 | |||
| daff119fe4 | |||
| e0d1ff42c0 | |||
| de413c56ae | |||
| da61b0290a | |||
| d03e6cedde | |||
| 7feb6ab962 | |||
| 854d4b7a53 | |||
| aa5999b188 | |||
| 37c5eab6f8 | |||
| 6daa2b9aeb | |||
| 5a5a2e5fa0 | |||
| 95c43fc675 | |||
| e7053c41f4 | |||
| fc6d4b4010 | |||
| 2893588016 | |||
| f2d4d816fb | |||
| 097d930668 | |||
| 7cab6824d1 | |||
| f77277a69e | |||
| 450128f9be | |||
| 3e35c974a4 | |||
| a14c22d4e9 | |||
| 58c65874ba | |||
| 27b0877714 | |||
| 5904f599a9 | |||
| df9e1d9854 | |||
| 75a22f82bd | |||
| a369130226 | |||
| 474024f9e6 | |||
| b4f4134e81 | |||
| cd64b67038 | |||
| 9af46df535 | |||
| b749866f0b | |||
| 60fa708f0b | |||
| 3b74077437 | |||
| 95d4bb2130 | |||
| f5dce6d960 | |||
| 1e98167b0e | |||
| 3eee2f6afa | |||
| ff4b60e1f3 | |||
| f91b73b938 | |||
| 05661c60ff | |||
| 625aca49de | |||
| 3bc0c36ace | |||
| eb0219988b | |||
| 705f792e87 | |||
| 716cf74190 | |||
| fc8dae2422 | |||
| 27353df0cc | |||
| 1a734adb4d | |||
| a9740b9133 | |||
| 62651c7114 | |||
| 1d728fc627 | |||
| 62ef2a2207 | |||
| 37aa8442dc | |||
| 5b0e828c10 | |||
| d5bfaef53d | |||
| bad732c26a | |||
| 1b92c95425 | |||
| d748c71845 | |||
| fc88ed1262 | |||
| 66f93035b0 | |||
| 9ff999cc2b | |||
| 4877eccc0d | |||
| f7d527cd28 | |||
| e29058c346 | |||
| 49894330d9 | |||
| cdc7d5f2ea | |||
| ec201623fb | |||
| 386091b79a | |||
| 1e4b7b5451 | |||
| 5cd178ba70 | |||
| 97eb9fdee8 | |||
| 5a04de231e | |||
| bb3509b5ff | |||
| 4a67905266 | |||
| c4e33d3168 | |||
| 872cdff2ab | |||
| 361d7001d0 | |||
| 7a02eee0f4 | |||
| cf45a8d807 | |||
| 435becbea0 | |||
| 0405bc74e9 | |||
| 4dab2a8555 | |||
| 3776d85e15 | |||
| d01ad4c499 | |||
| 28025a0f36 | |||
| 1220f784fe | |||
| 28f7d31e72 | |||
| 66936b0fff | |||
| 3a5507de95 | |||
| 86715fecc4 | |||
| 3062d3e070 | |||
| d89bfc32ac | |||
| 011c23761b | |||
| a8c8d2dd79 | |||
| 9f7ecd65e5 | |||
| f8e939d96f | |||
| 923af96d26 | |||
| a882e958b3 | |||
| 2cda629c8d | |||
| f033d2d8fb | |||
| a4bd88ab97 | |||
| f4616c8268 | |||
| 4712f0f3c1 | |||
| 6c1268f3b1 | |||
| 2e156b8990 | |||
| 3bfe6a1ef6 | |||
| 5c5069b622 | |||
| f8c6ddd4cb | |||
| e50a688ca3 | |||
| 334ab4707c | |||
| 87321942fe |
@@ -1,6 +1,5 @@
|
||||
name: Bug report
|
||||
description: Report an issue that should be fixed
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
name: 🚀 Feature Request
|
||||
description: Suggest an idea, feature, or enhancement
|
||||
labels: [discussion]
|
||||
title: "[FEATURE]:"
|
||||
|
||||
body:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
name: Question
|
||||
description: Ask a question
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: question
|
||||
|
||||
@@ -11,6 +11,6 @@ MrMushrooooom
|
||||
nexxeln
|
||||
R44VC0RP
|
||||
rekram1-node
|
||||
RhysSullivan
|
||||
thdxr
|
||||
simonklee
|
||||
vimtor
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Vouched contributors for this project.
|
||||
#
|
||||
# See https://github.com/mitchellh/vouch for details.
|
||||
#
|
||||
# Syntax:
|
||||
# - One handle per line (without @), sorted alphabetically.
|
||||
# - Optional platform prefix: platform:username (e.g., github:user).
|
||||
# - Denounce with minus prefix: -username or -platform:username.
|
||||
# - Optional details after a space following the handle.
|
||||
adamdotdevin
|
||||
-agusbasari29 AI PR slop
|
||||
ariane-emory
|
||||
-atharvau AI review spamming literally every PR
|
||||
-borealbytes
|
||||
-danieljoshuanazareth
|
||||
-danieljoshuanazareth
|
||||
edemaine
|
||||
-florianleibert
|
||||
fwang
|
||||
iamdavidhill
|
||||
jayair
|
||||
kitlangton
|
||||
kommander
|
||||
-opencode2026
|
||||
-opencodeengineer bot that spams issues
|
||||
r44vc0rp
|
||||
rekram1-node
|
||||
-ricardo-m-l
|
||||
-robinmordasiewicz
|
||||
rubdos
|
||||
shantur
|
||||
simonklee
|
||||
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
||||
thdxr
|
||||
-toastythebot
|
||||
@@ -1,5 +1,10 @@
|
||||
name: "Setup Bun"
|
||||
description: "Setup Bun with caching and install dependencies"
|
||||
inputs:
|
||||
install-flags:
|
||||
description: "Additional flags to pass to 'bun install'"
|
||||
required: false
|
||||
default: ""
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -18,7 +23,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
|
||||
bun-download-url: ${{ steps.bun-url.outputs.url }}
|
||||
@@ -29,7 +34,7 @@ runs:
|
||||
run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ steps.cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
@@ -46,8 +51,8 @@ runs:
|
||||
# e.g. ./patches/ for standard-openapi
|
||||
# https://github.com/oven-sh/bun/issues/28147
|
||||
if [ "$RUNNER_OS" = "Windows" ]; then
|
||||
bun install --linker hoisted
|
||||
bun install --linker hoisted ${{ inputs.install-flags }}
|
||||
else
|
||||
bun install
|
||||
bun install ${{ inputs.install-flags }}
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -19,7 +19,7 @@ runs:
|
||||
steps:
|
||||
- name: Create app token
|
||||
id: apptoken
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
|
||||
with:
|
||||
app-id: ${{ inputs.opencode-app-id }}
|
||||
private-key: ${{ inputs.opencode-app-secret }}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ jobs:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Close inactive PRs
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close non-compliant issues and PRs after 2 hours
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const { data: items } = await github.rest.issues.listForRepo({
|
||||
|
||||
@@ -21,18 +21,18 @@ jobs:
|
||||
REGISTRY: ghcr.io/${{ github.repository_owner }}
|
||||
TAG: "24.04"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
name: daily-issues-recap
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
|
||||
- cron: "0 23 * * *"
|
||||
workflow_dispatch: # Allow manual trigger for testing
|
||||
|
||||
jobs:
|
||||
daily-recap:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Generate daily issues recap
|
||||
id: recap
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENCODE_PERMISSION: |
|
||||
{
|
||||
"bash": {
|
||||
"*": "deny",
|
||||
"gh issue*": "allow",
|
||||
"gh search*": "allow"
|
||||
},
|
||||
"webfetch": "deny",
|
||||
"edit": "deny",
|
||||
"write": "deny"
|
||||
}
|
||||
run: |
|
||||
# Get today's date range
|
||||
TODAY=$(date -u +%Y-%m-%d)
|
||||
|
||||
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.
|
||||
|
||||
TODAY'S DATE: ${TODAY}
|
||||
|
||||
STEP 1: Gather today's issues
|
||||
Search for all OPEN issues created today (${TODAY}) using:
|
||||
gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
|
||||
|
||||
IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these:
|
||||
adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
|
||||
This recap is specifically for COMMUNITY (external) issues only.
|
||||
|
||||
STEP 2: Analyze and categorize
|
||||
For each issue created today, categorize it:
|
||||
|
||||
**Severity Assessment:**
|
||||
- CRITICAL: Crashes, data loss, security issues, blocks major functionality
|
||||
- HIGH: Significant bugs affecting many users, important features broken
|
||||
- MEDIUM: Bugs with workarounds, minor features broken
|
||||
- LOW: Minor issues, cosmetic, nice-to-haves
|
||||
|
||||
**Activity Assessment:**
|
||||
- Note issues with high comment counts or engagement
|
||||
- Note issues from repeat reporters (check if author has filed before)
|
||||
|
||||
STEP 3: Cross-reference with existing issues
|
||||
For issues that seem like feature requests or recurring bugs:
|
||||
- Search for similar older issues to identify patterns
|
||||
- Note if this is a frequently requested feature
|
||||
- Identify any issues that are duplicates of long-standing requests
|
||||
|
||||
STEP 4: Generate the recap
|
||||
Create a structured recap with these sections:
|
||||
|
||||
===DISCORD_START===
|
||||
**Daily Issues Recap - ${TODAY}**
|
||||
|
||||
**Summary Stats**
|
||||
- Total issues opened today: [count]
|
||||
- By category: [bugs/features/questions]
|
||||
|
||||
**Critical/High Priority Issues**
|
||||
[List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
|
||||
|
||||
**Most Active/Discussed**
|
||||
[Issues with significant engagement or from active community members]
|
||||
|
||||
**Trending Topics**
|
||||
[Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
|
||||
|
||||
**Duplicates & Related**
|
||||
[Issues that relate to existing open issues]
|
||||
===DISCORD_END===
|
||||
|
||||
STEP 5: Format for Discord
|
||||
Format the recap as a Discord-compatible message:
|
||||
- Use Discord markdown (**, __, etc.)
|
||||
- BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
|
||||
- Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
|
||||
- Group related issues on single lines where possible
|
||||
- Add emoji sparingly for critical items only
|
||||
- HARD LIMIT: Keep under 1800 characters total
|
||||
- Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
|
||||
- Prioritize signal over completeness - only surface what matters
|
||||
|
||||
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
|
||||
|
||||
# Extract only the Discord message between markers
|
||||
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
|
||||
|
||||
echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post to Discord
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
|
||||
run: |
|
||||
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
|
||||
cat /tmp/recap.txt
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read the recap
|
||||
RECAP_RAW=$(cat /tmp/recap.txt)
|
||||
RECAP_LENGTH=${#RECAP_RAW}
|
||||
|
||||
echo "Recap length: ${RECAP_LENGTH} chars"
|
||||
|
||||
# Function to post a message to Discord
|
||||
post_to_discord() {
|
||||
local msg="$1"
|
||||
local content=$(echo "$msg" | jq -Rs '.')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{\"content\": ${content}}" \
|
||||
"$DISCORD_WEBHOOK_URL"
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# If under limit, send as single message
|
||||
if [ "$RECAP_LENGTH" -le 1950 ]; then
|
||||
post_to_discord "$RECAP_RAW"
|
||||
else
|
||||
echo "Splitting into multiple messages..."
|
||||
remaining="$RECAP_RAW"
|
||||
while [ ${#remaining} -gt 0 ]; do
|
||||
if [ ${#remaining} -le 1950 ]; then
|
||||
post_to_discord "$remaining"
|
||||
break
|
||||
else
|
||||
chunk="${remaining:0:1900}"
|
||||
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
|
||||
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
|
||||
chunk="${remaining:0:$last_newline}"
|
||||
remaining="${remaining:$((last_newline+1))}"
|
||||
else
|
||||
chunk="${remaining:0:1900}"
|
||||
remaining="${remaining:1900}"
|
||||
fi
|
||||
post_to_discord "$chunk"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Posted daily recap to Discord"
|
||||
@@ -1,173 +0,0 @@
|
||||
name: daily-pr-recap
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
|
||||
- cron: "0 22 * * *"
|
||||
workflow_dispatch: # Allow manual trigger for testing
|
||||
|
||||
jobs:
|
||||
pr-recap:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Generate daily PR recap
|
||||
id: recap
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENCODE_PERMISSION: |
|
||||
{
|
||||
"bash": {
|
||||
"*": "deny",
|
||||
"gh pr*": "allow",
|
||||
"gh search*": "allow"
|
||||
},
|
||||
"webfetch": "deny",
|
||||
"edit": "deny",
|
||||
"write": "deny"
|
||||
}
|
||||
run: |
|
||||
TODAY=$(date -u +%Y-%m-%d)
|
||||
|
||||
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.
|
||||
|
||||
TODAY'S DATE: ${TODAY}
|
||||
|
||||
STEP 1: Gather PR data
|
||||
Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}):
|
||||
|
||||
# Open PRs created today
|
||||
gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
|
||||
|
||||
# Open PRs with activity today (updated today)
|
||||
gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
|
||||
|
||||
IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these:
|
||||
adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
|
||||
This recap is specifically for COMMUNITY (external) contributions only.
|
||||
|
||||
|
||||
|
||||
STEP 2: For high-activity PRs, check comment counts
|
||||
For promising PRs, run:
|
||||
gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
|
||||
|
||||
IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
|
||||
- copilot-pull-request-reviewer
|
||||
- github-actions
|
||||
|
||||
STEP 3: Identify what matters (ONLY from today's PRs)
|
||||
|
||||
**Bug Fixes From Today:**
|
||||
- PRs with 'fix' or 'bug' in title created/updated today
|
||||
- Small bug fixes (< 100 lines changed) that are easy to review
|
||||
- Bug fixes from community contributors
|
||||
|
||||
**High Activity Today:**
|
||||
- PRs with significant human comments today (excluding bots listed above)
|
||||
- PRs with back-and-forth discussion today
|
||||
|
||||
**Quick Wins:**
|
||||
- Small PRs (< 50 lines) that are approved or nearly approved
|
||||
- PRs that just need a final review
|
||||
|
||||
STEP 4: Generate the recap
|
||||
Create a structured recap:
|
||||
|
||||
===DISCORD_START===
|
||||
**Daily PR Recap - ${TODAY}**
|
||||
|
||||
**New PRs Today**
|
||||
[PRs opened today - group by type: bug fixes, features, etc.]
|
||||
|
||||
**Active PRs Today**
|
||||
[PRs with activity/updates today - significant discussion]
|
||||
|
||||
**Quick Wins**
|
||||
[Small PRs ready to merge]
|
||||
===DISCORD_END===
|
||||
|
||||
STEP 5: Format for Discord
|
||||
- Use Discord markdown (**, __, etc.)
|
||||
- BE EXTREMELY CONCISE - surface what we might miss
|
||||
- Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
|
||||
- Include PR author: [#1234](<url>) (@author)
|
||||
- For bug fixes, add brief description of what it fixes
|
||||
- Show line count for quick wins: \"(+15/-3 lines)\"
|
||||
- HARD LIMIT: Keep under 1800 characters total
|
||||
- Skip empty sections
|
||||
- Focus on PRs that need human eyes
|
||||
|
||||
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt
|
||||
|
||||
# Extract only the Discord message between markers
|
||||
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
|
||||
|
||||
echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post to Discord
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
|
||||
run: |
|
||||
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
|
||||
cat /tmp/pr_recap.txt
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read the recap
|
||||
RECAP_RAW=$(cat /tmp/pr_recap.txt)
|
||||
RECAP_LENGTH=${#RECAP_RAW}
|
||||
|
||||
echo "Recap length: ${RECAP_LENGTH} chars"
|
||||
|
||||
# Function to post a message to Discord
|
||||
post_to_discord() {
|
||||
local msg="$1"
|
||||
local content=$(echo "$msg" | jq -Rs '.')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{\"content\": ${content}}" \
|
||||
"$DISCORD_WEBHOOK_URL"
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# If under limit, send as single message
|
||||
if [ "$RECAP_LENGTH" -le 1950 ]; then
|
||||
post_to_discord "$RECAP_RAW"
|
||||
else
|
||||
echo "Splitting into multiple messages..."
|
||||
remaining="$RECAP_RAW"
|
||||
while [ ${#remaining} -gt 0 ]; do
|
||||
if [ ${#remaining} -le 1950 ]; then
|
||||
post_to_discord "$remaining"
|
||||
break
|
||||
else
|
||||
chunk="${remaining:0:1900}"
|
||||
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
|
||||
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
|
||||
chunk="${remaining:0:$last_newline}"
|
||||
remaining="${remaining:$((last_newline+1))}"
|
||||
else
|
||||
chunk="${remaining:0:1900}"
|
||||
remaining="${remaining:1900}"
|
||||
fi
|
||||
post_to_discord "$chunk"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Posted daily PR recap to Discord"
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
@@ -36,3 +36,10 @@ jobs:
|
||||
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||
HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: web@${{ github.sha }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_RELEASE: web@${{ github.sha }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full history to access commits
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Run opencode
|
||||
if: steps.commits.outputs.has_commits == 'true'
|
||||
uses: sst/opencode/github@latest
|
||||
uses: sst/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Nix
|
||||
uses: nixbuild/nix-quick-install-action@v34
|
||||
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
|
||||
|
||||
- name: Evaluate flake outputs (all systems)
|
||||
run: |
|
||||
|
||||
@@ -41,10 +41,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Nix
|
||||
uses: nixbuild/nix-quick-install-action@v34
|
||||
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
|
||||
|
||||
- name: Compute node_modules hash
|
||||
id: hash
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
echo "Computed hash for ${SYSTEM}: $HASH"
|
||||
|
||||
- name: Upload hash
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: hash-${{ matrix.system }}
|
||||
path: hash.txt
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
|
||||
|
||||
- name: Download hash artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: hashes
|
||||
pattern: hash-*
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Send nicely-formatted embed to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1
|
||||
uses: SethCohen/github-releases-to-discord@24d166886aee4646d448c8a389ff9e1ebcab3682 # v1.20.0
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
issues: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run opencode
|
||||
uses: anomalyco/opencode/github@latest
|
||||
uses: anomalyco/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_PERMISSION: '{"bash": "deny"}'
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Add Contributor Label
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const isPR = !!context.payload.pull_request;
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check PR standards
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check PR template compliance
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
+93
-233
@@ -35,7 +35,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
@@ -88,21 +88,21 @@ jobs:
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
./packages/opencode/script/build.ts
|
||||
./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli
|
||||
path: |
|
||||
packages/opencode/dist/opencode-darwin*
|
||||
packages/opencode/dist/opencode-linux*
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist/opencode-windows*
|
||||
@@ -123,9 +123,9 @@ jobs:
|
||||
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
|
||||
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist
|
||||
@@ -138,13 +138,13 @@ jobs:
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ env.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ env.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: azure/artifact-signing-action@v1
|
||||
- uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0
|
||||
with:
|
||||
endpoint: ${{ env.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
signing-account-name: ${{ env.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
--clobber `
|
||||
--repo "${{ needs.version.outputs.repo }}"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli-signed-windows
|
||||
path: |
|
||||
@@ -209,182 +209,6 @@ jobs:
|
||||
packages/opencode/dist/opencode-windows-x64
|
||||
packages/opencode/dist/opencode-windows-x64-baseline
|
||||
|
||||
build-tauri:
|
||||
needs:
|
||||
- build-cli
|
||||
- version
|
||||
continue-on-error: false
|
||||
env:
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
|
||||
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- host: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
# github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
|
||||
- host: windows-2025
|
||||
target: aarch64-pc-windows-msvc
|
||||
- host: blacksmith-4vcpu-windows-2025
|
||||
target: x86_64-pc-windows-msvc
|
||||
- host: blacksmith-4vcpu-ubuntu-2404
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- host: blacksmith-8vcpu-ubuntu-2404-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- uses: apple-actions/import-codesign-certs@v2
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
with:
|
||||
keychain: build
|
||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Verify Certificate
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
|
||||
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
||||
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
||||
echo "Certificate imported."
|
||||
|
||||
- name: Setup Apple API Key
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Azure login
|
||||
if: runner.os == 'Windows'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ env.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ env.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Cache apt packages
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/apt-cache
|
||||
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.settings.target }}-apt-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: |
|
||||
mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
sudo chmod -R a+rw ~/apt-cache
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.settings.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: packages/desktop/src-tauri
|
||||
shared-key: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
cd packages/desktop
|
||||
bun ./scripts/prepare.ts
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
|
||||
RUST_TARGET: ${{ matrix.settings.target }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
|
||||
- name: Resolve tauri portable SHA
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: echo "TAURI_PORTABLE_SHA=$(git ls-remote https://github.com/tauri-apps/tauri.git refs/heads/feat/truly-portable-appimage | cut -f1)" >> "$GITHUB_ENV"
|
||||
|
||||
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
|
||||
- name: Install tauri-cli from portable appimage branch
|
||||
uses: taiki-e/cache-cargo-install-action@v3
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
with:
|
||||
tool: tauri-cli
|
||||
git: https://github.com/tauri-apps/tauri
|
||||
# branch: feat/truly-portable-appimage
|
||||
rev: ${{ env.TAURI_PORTABLE_SHA }}
|
||||
|
||||
- name: Show tauri-cli version
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: cargo tauri --version
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build and upload artifacts
|
||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
timeout-minutes: 60
|
||||
with:
|
||||
projectPath: packages/desktop
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.version.outputs.release }}
|
||||
tagName: ${{ needs.version.outputs.tag }}
|
||||
releaseDraft: true
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }}
|
||||
releaseCommitish: ${{ github.sha }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
||||
|
||||
- name: Verify signed Windows desktop artifacts
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$files = @(
|
||||
"${{ github.workspace }}\packages\desktop\src-tauri\sidecars\opencode-cli-${{ matrix.settings.target }}.exe"
|
||||
)
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\src-tauri\target\${{ matrix.settings.target }}\release\bundle\nsis\*.exe" | Select-Object -ExpandProperty FullName
|
||||
|
||||
foreach ($file in $files) {
|
||||
$sig = Get-AuthenticodeSignature $file
|
||||
if ($sig.Status -ne "Valid") {
|
||||
throw "Invalid signature for ${file}: $($sig.Status)"
|
||||
}
|
||||
}
|
||||
|
||||
build-electron:
|
||||
needs:
|
||||
- build-cli
|
||||
@@ -402,12 +226,14 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
- host: macos-26-intel
|
||||
target: x86_64-apple-darwin
|
||||
platform_flag: --mac --x64
|
||||
- host: macos-latest
|
||||
bun_install_flags: --os=darwin --cpu=x64
|
||||
- host: macos-26
|
||||
target: aarch64-apple-darwin
|
||||
platform_flag: --mac --arm64
|
||||
bun_install_flags: --os=darwin --cpu=arm64
|
||||
# github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
|
||||
- host: "windows-2025"
|
||||
target: aarch64-pc-windows-msvc
|
||||
@@ -423,9 +249,9 @@ jobs:
|
||||
platform_flag: --linux
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: apple-actions/import-codesign-certs@v2
|
||||
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
|
||||
if: runner.os == 'macOS'
|
||||
with:
|
||||
keychain: build
|
||||
@@ -437,22 +263,24 @@ jobs:
|
||||
run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
install-flags: ${{ matrix.settings.bun_install_flags }}
|
||||
|
||||
- name: Azure login
|
||||
if: runner.os == 'Windows'
|
||||
uses: azure/login@v2
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ env.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ env.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Cache apt packages
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ~/apt-cache
|
||||
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||
@@ -476,7 +304,7 @@ jobs:
|
||||
|
||||
- name: Prepare
|
||||
run: bun ./scripts/prepare.ts
|
||||
working-directory: packages/desktop-electron
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
@@ -487,14 +315,21 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
working-directory: packages/desktop-electron
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
|
||||
- name: Package and publish
|
||||
if: needs.version.outputs.release
|
||||
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
|
||||
working-directory: packages/desktop-electron
|
||||
working-directory: packages/desktop
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
@@ -508,19 +343,43 @@ jobs:
|
||||
- name: Package (no publish)
|
||||
if: ${{ !needs.version.outputs.release }}
|
||||
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
|
||||
working-directory: packages/desktop-electron
|
||||
working-directory: packages/desktop
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
|
||||
- name: Create and upload macOS .app.tar.gz
|
||||
if: runner.os == 'macOS' && needs.version.outputs.release
|
||||
working-directory: packages/desktop/dist
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
run: |
|
||||
if [[ "${{ matrix.settings.target }}" == "x86_64-apple-darwin" ]]; then
|
||||
APP_DIR="mac"
|
||||
OUT_NAME="opencode-desktop-mac-x64.app.tar.gz"
|
||||
elif [[ "${{ matrix.settings.target }}" == "aarch64-apple-darwin" ]]; then
|
||||
APP_DIR="mac-arm64"
|
||||
OUT_NAME="opencode-desktop-mac-arm64.app.tar.gz"
|
||||
else
|
||||
echo "Unknown macOS target: ${{ matrix.settings.target }}"
|
||||
exit 1
|
||||
fi
|
||||
APP_PATH=$(find "$APP_DIR" -maxdepth 1 -name "*.app" -type d | head -1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
echo "No .app bundle found in $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
tar -czf "$OUT_NAME" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
|
||||
gh release upload "v${{ needs.version.outputs.version }}" "$OUT_NAME" --clobber --repo "${{ needs.version.outputs.repo }}"
|
||||
|
||||
- name: Verify signed Windows Electron artifacts
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$files = @()
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName
|
||||
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
|
||||
|
||||
foreach ($file in $files | Select-Object -Unique) {
|
||||
$sig = Get-AuthenticodeSignature $file
|
||||
@@ -529,49 +388,69 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-electron-${{ matrix.settings.target }}
|
||||
path: packages/desktop-electron/dist/*
|
||||
name: opencode-desktop-${{ matrix.settings.target }}
|
||||
path: packages/desktop/dist/*
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
name: latest-yml-${{ matrix.settings.target }}
|
||||
path: packages/desktop-electron/dist/latest*.yml
|
||||
path: packages/desktop/dist/latest*.yml
|
||||
|
||||
publish:
|
||||
needs:
|
||||
- version
|
||||
- build-cli
|
||||
- sign-cli-windows
|
||||
- build-tauri
|
||||
- build-electron
|
||||
if: always() && !failure() && !cancelled()
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: opencode-cli-signed-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
pattern: latest-yml-*
|
||||
path: /tmp/latest-yml
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
@@ -579,29 +458,8 @@ jobs:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: opencode-cli
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: opencode-cli-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: opencode-cli-signed-windows
|
||||
path: packages/opencode/dist
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
pattern: latest-yml-*
|
||||
path: /tmp/latest-yml
|
||||
|
||||
- name: Cache apt packages (AUR)
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||
@@ -628,3 +486,5 @@ jobs:
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
LATEST_YML_DIR: /tmp/latest-yml
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
release:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -45,13 +45,13 @@ jobs:
|
||||
|
||||
- name: Check PR guidelines compliance
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
|
||||
PR_TITLE: ${{ steps.pr-details.outputs.title }}
|
||||
run: |
|
||||
PR_BODY=$(jq -r .body pr_data.json)
|
||||
opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}'
|
||||
opencode run -m opencode/gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}'
|
||||
|
||||
<pr-number>
|
||||
${{ steps.pr-number.outputs.number }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Release Zed Extension
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
git config --global user.name "opencode"
|
||||
|
||||
- name: Cache Turbo
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: node_modules/.cache/turbo
|
||||
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }}
|
||||
@@ -68,9 +68,14 @@ jobs:
|
||||
env:
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||
|
||||
- name: Run HttpApi exerciser gates
|
||||
if: runner.os == 'Linux'
|
||||
working-directory: packages/opencode
|
||||
run: bun run test:httpapi
|
||||
|
||||
- name: Publish unit reports
|
||||
if: always()
|
||||
uses: mikepenz/action-junit-report@v6
|
||||
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
|
||||
with:
|
||||
report_paths: packages/*/.artifacts/unit/junit.xml
|
||||
check_name: "unit results (${{ matrix.settings.name }})"
|
||||
@@ -80,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Upload unit artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }}
|
||||
include-hidden-files: true
|
||||
@@ -106,12 +111,12 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
@@ -126,7 +131,7 @@ jobs:
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ github.workspace }}/.playwright-browsers
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium
|
||||
@@ -150,7 +155,7 @@ jobs:
|
||||
|
||||
- name: Upload Playwright artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
name: vouch-check-issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if issue author is denounced
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const author = context.payload.issue.user.login;
|
||||
const issueNumber = context.payload.issue.number;
|
||||
|
||||
// Skip bots
|
||||
if (author.endsWith('[bot]')) {
|
||||
core.info(`Skipping bot: ${author}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the VOUCHED.td file via API (no checkout needed)
|
||||
let content;
|
||||
try {
|
||||
const response = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: '.github/VOUCHED.td',
|
||||
});
|
||||
content = Buffer.from(response.data.content, 'base64').toString('utf-8');
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
core.info('No .github/VOUCHED.td file found, skipping check.');
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Parse the .td file for vouched and denounced users
|
||||
const vouched = new Set();
|
||||
const denounced = new Map();
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
|
||||
const isDenounced = trimmed.startsWith('-');
|
||||
const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
|
||||
if (!rest) continue;
|
||||
|
||||
const spaceIdx = rest.indexOf(' ');
|
||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||
|
||||
// Handle platform:username or bare username
|
||||
// Only match bare usernames or github: prefix (skip other platforms)
|
||||
const colonIdx = handle.indexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const platform = handle.slice(0, colonIdx).toLowerCase();
|
||||
if (platform !== 'github') continue;
|
||||
}
|
||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||
if (!username) continue;
|
||||
|
||||
if (isDenounced) {
|
||||
denounced.set(username.toLowerCase(), reason);
|
||||
continue;
|
||||
}
|
||||
|
||||
vouched.add(username.toLowerCase());
|
||||
}
|
||||
|
||||
// Check if the author is denounced
|
||||
const reason = denounced.get(author.toLowerCase());
|
||||
if (reason !== undefined) {
|
||||
// Author is denounced — close the issue
|
||||
const body = 'This issue has been automatically closed.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body,
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
state: 'closed',
|
||||
state_reason: 'not_planned',
|
||||
});
|
||||
|
||||
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Author is positively vouched — add label
|
||||
if (!vouched.has(author.toLowerCase())) {
|
||||
core.info(`User ${author} is not denounced or vouched. Allowing issue.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: ['Vouched'],
|
||||
});
|
||||
|
||||
core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
|
||||
@@ -1,114 +0,0 @@
|
||||
name: vouch-check-pr
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if PR author is denounced
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const author = context.payload.pull_request.user.login;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
// Skip bots
|
||||
if (author.endsWith('[bot]')) {
|
||||
core.info(`Skipping bot: ${author}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the VOUCHED.td file via API (no checkout needed)
|
||||
let content;
|
||||
try {
|
||||
const response = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: '.github/VOUCHED.td',
|
||||
});
|
||||
content = Buffer.from(response.data.content, 'base64').toString('utf-8');
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
core.info('No .github/VOUCHED.td file found, skipping check.');
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Parse the .td file for vouched and denounced users
|
||||
const vouched = new Set();
|
||||
const denounced = new Map();
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
|
||||
const isDenounced = trimmed.startsWith('-');
|
||||
const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
|
||||
if (!rest) continue;
|
||||
|
||||
const spaceIdx = rest.indexOf(' ');
|
||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||
|
||||
// Handle platform:username or bare username
|
||||
// Only match bare usernames or github: prefix (skip other platforms)
|
||||
const colonIdx = handle.indexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const platform = handle.slice(0, colonIdx).toLowerCase();
|
||||
if (platform !== 'github') continue;
|
||||
}
|
||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||
if (!username) continue;
|
||||
|
||||
if (isDenounced) {
|
||||
denounced.set(username.toLowerCase(), reason);
|
||||
continue;
|
||||
}
|
||||
|
||||
vouched.add(username.toLowerCase());
|
||||
}
|
||||
|
||||
// Check if the author is denounced
|
||||
const reason = denounced.get(author.toLowerCase());
|
||||
if (reason !== undefined) {
|
||||
// Author is denounced — close the PR
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: 'This pull request has been automatically closed.',
|
||||
});
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
state: 'closed',
|
||||
});
|
||||
|
||||
core.info(`Closed PR #${prNumber} from denounced user ${author}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Author is positively vouched — add label
|
||||
if (!vouched.has(author.toLowerCase())) {
|
||||
core.info(`User ${author} is not denounced or vouched. Allowing PR.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['Vouched'],
|
||||
});
|
||||
|
||||
core.info(`Added vouched label to PR #${prNumber} from ${author}`);
|
||||
@@ -1,38 +0,0 @@
|
||||
name: vouch-manage-by-issue
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: vouch-manage
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
manage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- uses: mitchellh/vouch/action/manage-by-issue@main
|
||||
with:
|
||||
issue-id: ${{ github.event.issue.number }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
roles: admin,maintain,write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
@@ -3,6 +3,7 @@ node_modules
|
||||
.worktrees
|
||||
.sst
|
||||
.env
|
||||
.env.local
|
||||
.idea
|
||||
.vscode
|
||||
.codex
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Fake secret-looking strings used by HTTP recorder redaction tests.
|
||||
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:69
|
||||
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:92
|
||||
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:146
|
||||
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:gcp-api-key:71
|
||||
@@ -1,899 +0,0 @@
|
||||
---
|
||||
description: Translate content for a specified locale while preserving technical terms
|
||||
mode: subagent
|
||||
model: opencode/gpt-5.4
|
||||
---
|
||||
|
||||
You are a professional translator and localization specialist.
|
||||
|
||||
Translate the user's content into the requested target locale (language + region, e.g. fr-FR, de-DE).
|
||||
|
||||
Requirements:
|
||||
|
||||
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
||||
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
|
||||
- Also preserve every term listed in the Do-Not-Translate glossary below.
|
||||
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
||||
- Do not modify fenced code blocks.
|
||||
- Output ONLY the translation (no commentary).
|
||||
|
||||
If the target locale is missing, ask the user to provide it.
|
||||
If no locale-specific glossary exists, use the global glossary only.
|
||||
|
||||
---
|
||||
|
||||
# Locale-Specific Glossaries
|
||||
|
||||
When a locale glossary exists, use it to:
|
||||
|
||||
- Apply preferred wording for recurring UI/docs terms in that locale
|
||||
- Preserve locale-specific do-not-translate terms and casing decisions
|
||||
- Prefer natural phrasing over literal translation when the locale file calls it out
|
||||
- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo)
|
||||
|
||||
Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below.
|
||||
|
||||
---
|
||||
|
||||
# Do-Not-Translate Terms (OpenCode Docs)
|
||||
|
||||
Generated from: `packages/web/src/content/docs/*.mdx` (default English docs)
|
||||
Generated on: 2026-02-10
|
||||
|
||||
Use this as a translation QA checklist / glossary. Preserve listed terms exactly (spelling, casing, punctuation).
|
||||
|
||||
General rules (verbatim, even if not listed below):
|
||||
|
||||
- Anything inside inline code (single backticks) or fenced code blocks (triple backticks)
|
||||
- MDX/JS code in docs: `import ... from "..."`, component tags, identifiers
|
||||
- CLI commands, flags, config keys/values, file paths, URLs/domains, and env vars
|
||||
|
||||
## Proper nouns and product names
|
||||
|
||||
Additional (not reliably captured via link text):
|
||||
|
||||
```text
|
||||
Astro
|
||||
Bun
|
||||
Chocolatey
|
||||
Cursor
|
||||
Docker
|
||||
Git
|
||||
GitHub Actions
|
||||
GitLab CI
|
||||
GNOME Terminal
|
||||
Homebrew
|
||||
Mise
|
||||
Neovim
|
||||
Node.js
|
||||
npm
|
||||
Obsidian
|
||||
opencode
|
||||
opencode-ai
|
||||
Paru
|
||||
pnpm
|
||||
ripgrep
|
||||
Scoop
|
||||
SST
|
||||
Starlight
|
||||
Visual Studio Code
|
||||
VS Code
|
||||
VSCodium
|
||||
Windsurf
|
||||
Windows Terminal
|
||||
Yarn
|
||||
Zellij
|
||||
Zed
|
||||
anomalyco
|
||||
```
|
||||
|
||||
Extracted from link labels in the English docs (review and prune as desired):
|
||||
|
||||
```text
|
||||
@openspoon/subtask2
|
||||
302.AI console
|
||||
ACP progress report
|
||||
Agent Client Protocol
|
||||
Agent Skills
|
||||
Agentic
|
||||
AGENTS.md
|
||||
AI SDK
|
||||
Alacritty
|
||||
Anthropic
|
||||
Anthropic's Data Policies
|
||||
Atom One
|
||||
Avante.nvim
|
||||
Ayu
|
||||
Azure AI Foundry
|
||||
Azure portal
|
||||
Baseten
|
||||
built-in GITHUB_TOKEN
|
||||
Bun.$
|
||||
Catppuccin
|
||||
Cerebras console
|
||||
ChatGPT Plus or Pro
|
||||
Cloudflare dashboard
|
||||
CodeCompanion.nvim
|
||||
CodeNomad
|
||||
Configuring Adapters: Environment Variables
|
||||
Context7 MCP server
|
||||
Cortecs console
|
||||
Deep Infra dashboard
|
||||
DeepSeek console
|
||||
Duo Agent Platform
|
||||
Everforest
|
||||
Fireworks AI console
|
||||
Firmware dashboard
|
||||
Ghostty
|
||||
GitLab CLI agents docs
|
||||
GitLab docs
|
||||
GitLab User Settings > Access Tokens
|
||||
Granular Rules (Object Syntax)
|
||||
Grep by Vercel
|
||||
Groq console
|
||||
Gruvbox
|
||||
Helicone
|
||||
Helicone documentation
|
||||
Helicone Header Directory
|
||||
Helicone's Model Directory
|
||||
Hugging Face Inference Providers
|
||||
Hugging Face settings
|
||||
install WSL
|
||||
IO.NET console
|
||||
JetBrains IDE
|
||||
Kanagawa
|
||||
Kitty
|
||||
MiniMax API Console
|
||||
Models.dev
|
||||
Moonshot AI console
|
||||
Nebius Token Factory console
|
||||
Nord
|
||||
OAuth
|
||||
Ollama integration docs
|
||||
OpenAI's Data Policies
|
||||
OpenChamber
|
||||
OpenCode
|
||||
OpenCode config
|
||||
OpenCode Config
|
||||
OpenCode TUI with the opencode theme
|
||||
OpenCode Web - Active Session
|
||||
OpenCode Web - New Session
|
||||
OpenCode Web - See Servers
|
||||
OpenCode Zen
|
||||
OpenCode-Obsidian
|
||||
OpenRouter dashboard
|
||||
OpenWork
|
||||
OVHcloud panel
|
||||
Pro+ subscription
|
||||
SAP BTP Cockpit
|
||||
Scaleway Console IAM settings
|
||||
Scaleway Generative APIs
|
||||
SDK documentation
|
||||
Sentry MCP server
|
||||
shell API
|
||||
Together AI console
|
||||
Tokyonight
|
||||
Unified Billing
|
||||
Venice AI console
|
||||
Vercel dashboard
|
||||
WezTerm
|
||||
Windows Subsystem for Linux (WSL)
|
||||
WSL
|
||||
WSL (Windows Subsystem for Linux)
|
||||
WSL extension
|
||||
xAI console
|
||||
Z.AI API console
|
||||
Zed
|
||||
ZenMux dashboard
|
||||
Zod
|
||||
```
|
||||
|
||||
## Acronyms and initialisms
|
||||
|
||||
```text
|
||||
ACP
|
||||
AGENTS
|
||||
AI
|
||||
AI21
|
||||
ANSI
|
||||
API
|
||||
AST
|
||||
AWS
|
||||
BTP
|
||||
CD
|
||||
CDN
|
||||
CI
|
||||
CLI
|
||||
CMD
|
||||
CORS
|
||||
DEBUG
|
||||
EKS
|
||||
ERROR
|
||||
FAQ
|
||||
GLM
|
||||
GNOME
|
||||
GPT
|
||||
HTML
|
||||
HTTP
|
||||
HTTPS
|
||||
IAM
|
||||
ID
|
||||
IDE
|
||||
INFO
|
||||
IO
|
||||
IP
|
||||
IRSA
|
||||
JS
|
||||
JSON
|
||||
JSONC
|
||||
K2
|
||||
LLM
|
||||
LM
|
||||
LSP
|
||||
M2
|
||||
MCP
|
||||
MR
|
||||
NET
|
||||
NPM
|
||||
NTLM
|
||||
OIDC
|
||||
OS
|
||||
PAT
|
||||
PATH
|
||||
PHP
|
||||
PR
|
||||
PTY
|
||||
README
|
||||
RFC
|
||||
RPC
|
||||
SAP
|
||||
SDK
|
||||
SKILL
|
||||
SSE
|
||||
SSO
|
||||
TS
|
||||
TTY
|
||||
TUI
|
||||
UI
|
||||
URL
|
||||
US
|
||||
UX
|
||||
VCS
|
||||
VPC
|
||||
VPN
|
||||
VS
|
||||
WARN
|
||||
WSL
|
||||
X11
|
||||
YAML
|
||||
```
|
||||
|
||||
## Code identifiers used in prose (CamelCase, mixedCase)
|
||||
|
||||
```text
|
||||
apiKey
|
||||
AppleScript
|
||||
AssistantMessage
|
||||
baseURL
|
||||
BurntSushi
|
||||
ChatGPT
|
||||
ClangFormat
|
||||
CodeCompanion
|
||||
CodeNomad
|
||||
DeepSeek
|
||||
DefaultV2
|
||||
FileContent
|
||||
FileDiff
|
||||
FileNode
|
||||
fineGrained
|
||||
FormatterStatus
|
||||
GitHub
|
||||
GitLab
|
||||
iTerm2
|
||||
JavaScript
|
||||
JetBrains
|
||||
macOS
|
||||
mDNS
|
||||
MiniMax
|
||||
NeuralNomadsAI
|
||||
NickvanDyke
|
||||
NoeFabris
|
||||
OpenAI
|
||||
OpenAPI
|
||||
OpenChamber
|
||||
OpenCode
|
||||
OpenRouter
|
||||
OpenTUI
|
||||
OpenWork
|
||||
ownUserPermissions
|
||||
PowerShell
|
||||
ProviderAuthAuthorization
|
||||
ProviderAuthMethod
|
||||
ProviderInitError
|
||||
SessionStatus
|
||||
TabItem
|
||||
tokenType
|
||||
ToolIDs
|
||||
ToolList
|
||||
TypeScript
|
||||
typesUrl
|
||||
UserMessage
|
||||
VcsInfo
|
||||
WebView2
|
||||
WezTerm
|
||||
xAI
|
||||
ZenMux
|
||||
```
|
||||
|
||||
## OpenCode CLI commands (as shown in docs)
|
||||
|
||||
```text
|
||||
opencode
|
||||
opencode [project]
|
||||
opencode /path/to/project
|
||||
opencode acp
|
||||
opencode agent [command]
|
||||
opencode agent create
|
||||
opencode agent list
|
||||
opencode attach [url]
|
||||
opencode attach http://10.20.30.40:4096
|
||||
opencode attach http://localhost:4096
|
||||
opencode auth [command]
|
||||
opencode auth list
|
||||
opencode auth login
|
||||
opencode auth logout
|
||||
opencode auth ls
|
||||
opencode export [sessionID]
|
||||
opencode github [command]
|
||||
opencode github install
|
||||
opencode github run
|
||||
opencode import <file>
|
||||
opencode import https://opncd.ai/s/abc123
|
||||
opencode import session.json
|
||||
opencode mcp [command]
|
||||
opencode mcp add
|
||||
opencode mcp auth [name]
|
||||
opencode mcp auth list
|
||||
opencode mcp auth ls
|
||||
opencode mcp auth my-oauth-server
|
||||
opencode mcp auth sentry
|
||||
opencode mcp debug <name>
|
||||
opencode mcp debug my-oauth-server
|
||||
opencode mcp list
|
||||
opencode mcp logout [name]
|
||||
opencode mcp logout my-oauth-server
|
||||
opencode mcp ls
|
||||
opencode models --refresh
|
||||
opencode models [provider]
|
||||
opencode models anthropic
|
||||
opencode run [message..]
|
||||
opencode run Explain the use of context in Go
|
||||
opencode serve
|
||||
opencode serve --cors http://localhost:5173 --cors https://app.example.com
|
||||
opencode serve --hostname 0.0.0.0 --port 4096
|
||||
opencode serve [--port <number>] [--hostname <string>] [--cors <origin>]
|
||||
opencode session [command]
|
||||
opencode session list
|
||||
opencode session delete <sessionID>
|
||||
opencode stats
|
||||
opencode uninstall
|
||||
opencode upgrade
|
||||
opencode upgrade [target]
|
||||
opencode upgrade v0.1.48
|
||||
opencode web
|
||||
opencode web --cors https://example.com
|
||||
opencode web --hostname 0.0.0.0
|
||||
opencode web --mdns
|
||||
opencode web --mdns --mdns-domain myproject.local
|
||||
opencode web --port 4096
|
||||
opencode web --port 4096 --hostname 0.0.0.0
|
||||
opencode.server.close()
|
||||
```
|
||||
|
||||
## Slash commands and routes
|
||||
|
||||
```text
|
||||
/agent
|
||||
/auth/:id
|
||||
/clear
|
||||
/command
|
||||
/config
|
||||
/config/providers
|
||||
/connect
|
||||
/continue
|
||||
/doc
|
||||
/editor
|
||||
/event
|
||||
/experimental/tool?provider=<p>&model=<m>
|
||||
/experimental/tool/ids
|
||||
/export
|
||||
/file?path=<path>
|
||||
/file/content?path=<p>
|
||||
/file/status
|
||||
/find?pattern=<pat>
|
||||
/find/file
|
||||
/find/file?query=<q>
|
||||
/find/symbol?query=<q>
|
||||
/formatter
|
||||
/global/event
|
||||
/global/health
|
||||
/help
|
||||
/init
|
||||
/instance/dispose
|
||||
/log
|
||||
/lsp
|
||||
/mcp
|
||||
/mnt/
|
||||
/mnt/c/
|
||||
/mnt/d/
|
||||
/models
|
||||
/oc
|
||||
/opencode
|
||||
/path
|
||||
/project
|
||||
/project/current
|
||||
/provider
|
||||
/provider/{id}/oauth/authorize
|
||||
/provider/{id}/oauth/callback
|
||||
/provider/auth
|
||||
/q
|
||||
/quit
|
||||
/redo
|
||||
/resume
|
||||
/session
|
||||
/session/:id
|
||||
/session/:id/abort
|
||||
/session/:id/children
|
||||
/session/:id/command
|
||||
/session/:id/diff
|
||||
/session/:id/fork
|
||||
/session/:id/init
|
||||
/session/:id/message
|
||||
/session/:id/message/:messageID
|
||||
/session/:id/permissions/:permissionID
|
||||
/session/:id/prompt_async
|
||||
/session/:id/revert
|
||||
/session/:id/share
|
||||
/session/:id/shell
|
||||
/session/:id/summarize
|
||||
/session/:id/todo
|
||||
/session/:id/unrevert
|
||||
/session/status
|
||||
/share
|
||||
/summarize
|
||||
/theme
|
||||
/tui
|
||||
/tui/append-prompt
|
||||
/tui/clear-prompt
|
||||
/tui/control/next
|
||||
/tui/control/response
|
||||
/tui/execute-command
|
||||
/tui/open-help
|
||||
/tui/open-models
|
||||
/tui/open-sessions
|
||||
/tui/open-themes
|
||||
/tui/show-toast
|
||||
/tui/submit-prompt
|
||||
/undo
|
||||
/Users/username
|
||||
/Users/username/projects/*
|
||||
/vcs
|
||||
```
|
||||
|
||||
## CLI flags and short options
|
||||
|
||||
```text
|
||||
--agent
|
||||
--attach
|
||||
--command
|
||||
--continue
|
||||
--cors
|
||||
--cwd
|
||||
--days
|
||||
--dir
|
||||
--dry-run
|
||||
--event
|
||||
--file
|
||||
--force
|
||||
--fork
|
||||
--format
|
||||
--help
|
||||
--hostname
|
||||
--hostname 0.0.0.0
|
||||
--keep-config
|
||||
--keep-data
|
||||
--log-level
|
||||
--max-count
|
||||
--mdns
|
||||
--mdns-domain
|
||||
--method
|
||||
--model
|
||||
--models
|
||||
--port
|
||||
--print-logs
|
||||
--project
|
||||
--prompt
|
||||
--refresh
|
||||
--session
|
||||
--share
|
||||
--title
|
||||
--token
|
||||
--tools
|
||||
--verbose
|
||||
--version
|
||||
--wait
|
||||
|
||||
-c
|
||||
-d
|
||||
-f
|
||||
-h
|
||||
-m
|
||||
-n
|
||||
-s
|
||||
-v
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
```text
|
||||
AI_API_URL
|
||||
AI_FLOW_CONTEXT
|
||||
AI_FLOW_EVENT
|
||||
AI_FLOW_INPUT
|
||||
AICORE_DEPLOYMENT_ID
|
||||
AICORE_RESOURCE_GROUP
|
||||
AICORE_SERVICE_KEY
|
||||
ANTHROPIC_API_KEY
|
||||
AWS_ACCESS_KEY_ID
|
||||
AWS_BEARER_TOKEN_BEDROCK
|
||||
AWS_PROFILE
|
||||
AWS_REGION
|
||||
AWS_ROLE_ARN
|
||||
AWS_SECRET_ACCESS_KEY
|
||||
AWS_WEB_IDENTITY_TOKEN_FILE
|
||||
AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
|
||||
AZURE_RESOURCE_NAME
|
||||
CI_PROJECT_DIR
|
||||
CI_SERVER_FQDN
|
||||
CI_WORKLOAD_REF
|
||||
CLOUDFLARE_ACCOUNT_ID
|
||||
CLOUDFLARE_API_TOKEN
|
||||
CLOUDFLARE_GATEWAY_ID
|
||||
CONTEXT7_API_KEY
|
||||
GITHUB_TOKEN
|
||||
GITLAB_AI_GATEWAY_URL
|
||||
GITLAB_HOST
|
||||
GITLAB_INSTANCE_URL
|
||||
GITLAB_OAUTH_CLIENT_ID
|
||||
GITLAB_TOKEN
|
||||
GITLAB_TOKEN_OPENCODE
|
||||
GOOGLE_APPLICATION_CREDENTIALS
|
||||
GOOGLE_CLOUD_PROJECT
|
||||
HTTP_PROXY
|
||||
HTTPS_PROXY
|
||||
K2_
|
||||
MY_API_KEY
|
||||
MY_ENV_VAR
|
||||
MY_MCP_CLIENT_ID
|
||||
MY_MCP_CLIENT_SECRET
|
||||
NO_PROXY
|
||||
NODE_ENV
|
||||
NODE_EXTRA_CA_CERTS
|
||||
NPM_AUTH_TOKEN
|
||||
OC_ALLOW_WAYLAND
|
||||
OPENCODE_API_KEY
|
||||
OPENCODE_AUTH_JSON
|
||||
OPENCODE_AUTO_SHARE
|
||||
OPENCODE_CLIENT
|
||||
OPENCODE_CONFIG
|
||||
OPENCODE_CONFIG_CONTENT
|
||||
OPENCODE_CONFIG_DIR
|
||||
OPENCODE_DISABLE_AUTOCOMPACT
|
||||
OPENCODE_DISABLE_AUTOUPDATE
|
||||
OPENCODE_DISABLE_CLAUDE_CODE
|
||||
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT
|
||||
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS
|
||||
OPENCODE_DISABLE_DEFAULT_PLUGINS
|
||||
OPENCODE_DISABLE_LSP_DOWNLOAD
|
||||
OPENCODE_DISABLE_MODELS_FETCH
|
||||
OPENCODE_DISABLE_PRUNE
|
||||
OPENCODE_DISABLE_TERMINAL_TITLE
|
||||
OPENCODE_ENABLE_EXA
|
||||
OPENCODE_ENABLE_EXPERIMENTAL_MODELS
|
||||
OPENCODE_EXPERIMENTAL
|
||||
OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER
|
||||
OPENCODE_EXPERIMENTAL_EXA
|
||||
OPENCODE_EXPERIMENTAL_FILEWATCHER
|
||||
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY
|
||||
OPENCODE_EXPERIMENTAL_LSP_TOOL
|
||||
OPENCODE_EXPERIMENTAL_LSP_TY
|
||||
OPENCODE_EXPERIMENTAL_MARKDOWN
|
||||
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX
|
||||
OPENCODE_EXPERIMENTAL_OXFMT
|
||||
OPENCODE_EXPERIMENTAL_PLAN_MODE
|
||||
OPENCODE_ENABLE_QUESTION_TOOL
|
||||
OPENCODE_FAKE_VCS
|
||||
OPENCODE_GIT_BASH_PATH
|
||||
OPENCODE_MODEL
|
||||
OPENCODE_MODELS_URL
|
||||
OPENCODE_PERMISSION
|
||||
OPENCODE_PORT
|
||||
OPENCODE_SERVER_PASSWORD
|
||||
OPENCODE_SERVER_USERNAME
|
||||
PROJECT_ROOT
|
||||
RESOURCE_NAME
|
||||
RUST_LOG
|
||||
VARIABLE_NAME
|
||||
VERTEX_LOCATION
|
||||
XDG_CONFIG_HOME
|
||||
```
|
||||
|
||||
## Package/module identifiers
|
||||
|
||||
```text
|
||||
../../../config.mjs
|
||||
@astrojs/starlight/components
|
||||
@opencode-ai/plugin
|
||||
@opencode-ai/sdk
|
||||
path
|
||||
shescape
|
||||
zod
|
||||
|
||||
@
|
||||
@ai-sdk/anthropic
|
||||
@ai-sdk/cerebras
|
||||
@ai-sdk/google
|
||||
@ai-sdk/openai
|
||||
@ai-sdk/openai-compatible
|
||||
@File#L37-42
|
||||
@modelcontextprotocol/server-everything
|
||||
@opencode
|
||||
```
|
||||
|
||||
## GitHub owner/repo slugs referenced in docs
|
||||
|
||||
```text
|
||||
24601/opencode-zellij-namer
|
||||
angristan/opencode-wakatime
|
||||
anomalyco/opencode
|
||||
apps/opencode-agent
|
||||
athal7/opencode-devcontainers
|
||||
awesome-opencode/awesome-opencode
|
||||
backnotprop/plannotator
|
||||
ben-vargas/ai-sdk-provider-opencode-sdk
|
||||
btriapitsyn/openchamber
|
||||
BurntSushi/ripgrep
|
||||
Cluster444/agentic
|
||||
code-yeongyu/oh-my-opencode
|
||||
darrenhinde/opencode-agents
|
||||
different-ai/opencode-scheduler
|
||||
different-ai/openwork
|
||||
features/copilot
|
||||
folke/tokyonight.nvim
|
||||
franlol/opencode-md-table-formatter
|
||||
ggml-org/llama.cpp
|
||||
ghoulr/opencode-websearch-cited.git
|
||||
H2Shami/opencode-helicone-session
|
||||
hosenur/portal
|
||||
jamesmurdza/daytona
|
||||
jenslys/opencode-gemini-auth
|
||||
JRedeker/opencode-morph-fast-apply
|
||||
JRedeker/opencode-shell-strategy
|
||||
kdcokenny/ocx
|
||||
kdcokenny/opencode-background-agents
|
||||
kdcokenny/opencode-notify
|
||||
kdcokenny/opencode-workspace
|
||||
kdcokenny/opencode-worktree
|
||||
login/device
|
||||
mohak34/opencode-notifier
|
||||
morhetz/gruvbox
|
||||
mtymek/opencode-obsidian
|
||||
NeuralNomadsAI/CodeNomad
|
||||
nick-vi/opencode-type-inject
|
||||
NickvanDyke/opencode.nvim
|
||||
NoeFabris/opencode-antigravity-auth
|
||||
nordtheme/nord
|
||||
numman-ali/opencode-openai-codex-auth
|
||||
olimorris/codecompanion.nvim
|
||||
panta82/opencode-notificator
|
||||
rebelot/kanagawa.nvim
|
||||
remorses/kimaki
|
||||
sainnhe/everforest
|
||||
shekohex/opencode-google-antigravity-auth
|
||||
shekohex/opencode-pty.git
|
||||
spoons-and-mirrors/subtask2
|
||||
sudo-tee/opencode.nvim
|
||||
supermemoryai/opencode-supermemory
|
||||
Tarquinen/opencode-dynamic-context-pruning
|
||||
Th3Whit3Wolf/one-nvim
|
||||
upstash/context7
|
||||
vtemian/micode
|
||||
vtemian/octto
|
||||
yetone/avante.nvim
|
||||
zenobi-us/opencode-plugin-template
|
||||
zenobi-us/opencode-skillful
|
||||
```
|
||||
|
||||
## Paths, filenames, globs, and URLs
|
||||
|
||||
```text
|
||||
./.opencode/themes/*.json
|
||||
./<project-slug>/storage/
|
||||
./config/#custom-directory
|
||||
./global/storage/
|
||||
.agents/skills/*/SKILL.md
|
||||
.agents/skills/<name>/SKILL.md
|
||||
.clang-format
|
||||
.claude
|
||||
.claude/skills
|
||||
.claude/skills/*/SKILL.md
|
||||
.claude/skills/<name>/SKILL.md
|
||||
.env
|
||||
.github/workflows/opencode.yml
|
||||
.gitignore
|
||||
.gitlab-ci.yml
|
||||
.ignore
|
||||
.NET SDK
|
||||
.npmrc
|
||||
.ocamlformat
|
||||
.opencode
|
||||
.opencode/
|
||||
.opencode/agents/
|
||||
.opencode/commands/
|
||||
.opencode/commands/test.md
|
||||
.opencode/modes/
|
||||
.opencode/plans/*.md
|
||||
.opencode/plugins/
|
||||
.opencode/skills/<name>/SKILL.md
|
||||
.opencode/skills/git-release/SKILL.md
|
||||
.opencode/tools/
|
||||
.well-known/opencode
|
||||
{ type: "raw" \| "patch", content: string }
|
||||
{file:path/to/file}
|
||||
**/*.js
|
||||
%USERPROFILE%/intelephense/license.txt
|
||||
%USERPROFILE%\.cache\opencode
|
||||
%USERPROFILE%\.config\opencode\opencode.jsonc
|
||||
%USERPROFILE%\.config\opencode\plugins
|
||||
%USERPROFILE%\.local\share\opencode
|
||||
%USERPROFILE%\.local\share\opencode\log
|
||||
<project-root>/.opencode/themes/*.json
|
||||
<providerId>/<modelId>
|
||||
<your-project>/.opencode/plugins/
|
||||
~
|
||||
~/...
|
||||
~/.agents/skills/*/SKILL.md
|
||||
~/.agents/skills/<name>/SKILL.md
|
||||
~/.aws/credentials
|
||||
~/.bashrc
|
||||
~/.cache/opencode
|
||||
~/.cache/opencode/node_modules/
|
||||
~/.claude/CLAUDE.md
|
||||
~/.claude/skills/
|
||||
~/.claude/skills/*/SKILL.md
|
||||
~/.claude/skills/<name>/SKILL.md
|
||||
~/.config/opencode
|
||||
~/.config/opencode/AGENTS.md
|
||||
~/.config/opencode/agents/
|
||||
~/.config/opencode/commands/
|
||||
~/.config/opencode/modes/
|
||||
~/.config/opencode/opencode.json
|
||||
~/.config/opencode/opencode.jsonc
|
||||
~/.config/opencode/plugins/
|
||||
~/.config/opencode/skills/*/SKILL.md
|
||||
~/.config/opencode/skills/<name>/SKILL.md
|
||||
~/.config/opencode/themes/*.json
|
||||
~/.config/opencode/tools/
|
||||
~/.config/zed/settings.json
|
||||
~/.local/share
|
||||
~/.local/share/opencode/
|
||||
~/.local/share/opencode/auth.json
|
||||
~/.local/share/opencode/log/
|
||||
~/.local/share/opencode/mcp-auth.json
|
||||
~/.local/share/opencode/opencode.jsonc
|
||||
~/.npmrc
|
||||
~/.zshrc
|
||||
~/code/
|
||||
~/Library/Application Support
|
||||
~/projects/*
|
||||
~/projects/personal/
|
||||
${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts
|
||||
$HOME/intelephense/license.txt
|
||||
$HOME/projects/*
|
||||
$XDG_CONFIG_HOME/opencode/themes/*.json
|
||||
agent/
|
||||
agents/
|
||||
build/
|
||||
commands/
|
||||
dist/
|
||||
http://<wsl-ip>:4096
|
||||
http://127.0.0.1:8080/callback
|
||||
http://localhost:<port>
|
||||
http://localhost:4096
|
||||
http://localhost:4096/doc
|
||||
https://app.example.com
|
||||
https://AZURE_COGNITIVE_SERVICES_RESOURCE_NAME.cognitiveservices.azure.com/
|
||||
https://opencode.ai/zen/v1/chat/completions
|
||||
https://opencode.ai/zen/v1/messages
|
||||
https://opencode.ai/zen/v1/models/gemini-3-flash
|
||||
https://opencode.ai/zen/v1/models/gemini-3-pro
|
||||
https://opencode.ai/zen/v1/responses
|
||||
https://RESOURCE_NAME.openai.azure.com/
|
||||
laravel/pint
|
||||
log/
|
||||
model: "anthropic/claude-sonnet-4-5"
|
||||
modes/
|
||||
node_modules/
|
||||
openai/gpt-4.1
|
||||
opencode.ai/config.json
|
||||
opencode/<model-id>
|
||||
opencode/gpt-5.1-codex
|
||||
opencode/gpt-5.2-codex
|
||||
opencode/kimi-k2
|
||||
openrouter/google/gemini-2.5-flash
|
||||
opncd.ai/s/<share-id>
|
||||
packages/*/AGENTS.md
|
||||
plugins/
|
||||
project/
|
||||
provider_id/model_id
|
||||
provider/model
|
||||
provider/model-id
|
||||
rm -rf ~/.cache/opencode
|
||||
skills/
|
||||
skills/*/SKILL.md
|
||||
src/**/*.ts
|
||||
themes/
|
||||
tools/
|
||||
```
|
||||
|
||||
## Keybind strings
|
||||
|
||||
```text
|
||||
alt+b
|
||||
Alt+Ctrl+K
|
||||
alt+d
|
||||
alt+f
|
||||
Cmd+Esc
|
||||
Cmd+Option+K
|
||||
Cmd+Shift+Esc
|
||||
Cmd+Shift+G
|
||||
Cmd+Shift+P
|
||||
ctrl+a
|
||||
ctrl+b
|
||||
ctrl+d
|
||||
ctrl+e
|
||||
Ctrl+Esc
|
||||
ctrl+f
|
||||
ctrl+g
|
||||
ctrl+k
|
||||
Ctrl+Shift+Esc
|
||||
Ctrl+Shift+P
|
||||
ctrl+t
|
||||
ctrl+u
|
||||
ctrl+w
|
||||
ctrl+x
|
||||
DELETE
|
||||
Shift+Enter
|
||||
WIN+R
|
||||
```
|
||||
|
||||
## Model ID strings referenced
|
||||
|
||||
```text
|
||||
{env:OPENCODE_MODEL}
|
||||
anthropic/claude-3-5-sonnet-20241022
|
||||
anthropic/claude-haiku-4-20250514
|
||||
anthropic/claude-haiku-4-5
|
||||
anthropic/claude-sonnet-4-20250514
|
||||
anthropic/claude-sonnet-4-5
|
||||
gitlab/duo-chat-haiku-4-5
|
||||
lmstudio/google/gemma-3n-e4b
|
||||
openai/gpt-4.1
|
||||
openai/gpt-5
|
||||
opencode/gpt-5.1-codex
|
||||
opencode/gpt-5.2-codex
|
||||
opencode/kimi-k2
|
||||
openrouter/google/gemini-2.5-flash
|
||||
```
|
||||
+15
-112
@@ -1,7 +1,7 @@
|
||||
---
|
||||
mode: primary
|
||||
hidden: true
|
||||
model: opencode/minimax-m2.5
|
||||
model: opencode/gpt-5.4-nano
|
||||
color: "#44BA81"
|
||||
tools:
|
||||
"*": false
|
||||
@@ -14,127 +14,30 @@ Use your github-triage tool to triage issues.
|
||||
|
||||
This file is the source of truth for ownership/routing rules.
|
||||
|
||||
## Labels
|
||||
Assign issues by choosing the team with the strongest overlap. The github-triage tool will assign a random member from that team.
|
||||
|
||||
### windows
|
||||
Do not add labels to issues. Only assign an owner.
|
||||
|
||||
Use for any issue that mentions Windows (the OS). Be sure they are saying that they are on Windows.
|
||||
When calling github-triage, pass one of these team values: tui, desktop_web, core, inference, windows.
|
||||
|
||||
- Use if they mention WSL too
|
||||
## Teams
|
||||
|
||||
#### perf
|
||||
### TUI
|
||||
|
||||
Performance-related issues:
|
||||
Terminal UI issues, including rendering, keybindings, scrolling, terminal compatibility, SSH behavior, crashes in the TUI, and low-level TUI performance.
|
||||
|
||||
- Slow performance
|
||||
- High RAM usage
|
||||
- High CPU usage
|
||||
### Desktop / Web
|
||||
|
||||
**Only** add if it's likely a RAM or CPU issue. **Do not** add for LLM slowness.
|
||||
Desktop application and browser-based app issues, including `opencode web`, desktop-specific UI behavior, packaging, and web view problems.
|
||||
|
||||
#### desktop
|
||||
### Core
|
||||
|
||||
Desktop app issues:
|
||||
Core opencode server and harness issues, including sqlite, snapshots, memory, API behavior, agent context construction, tool execution, provider integrations, model behavior, documentation, and larger architectural features.
|
||||
|
||||
- `opencode web` command
|
||||
- The desktop app itself
|
||||
### Inference
|
||||
|
||||
**Only** add if it's specifically about the Desktop application or `opencode web` view. **Do not** add for terminal, TUI, or general opencode issues.
|
||||
OpenCode Zen, OpenCode Go, and billing issues.
|
||||
|
||||
#### nix
|
||||
### Windows
|
||||
|
||||
**Only** add if the issue explicitly mentions nix.
|
||||
|
||||
If the issue does not mention nix, do not add nix.
|
||||
|
||||
If the issue mentions nix, assign to `rekram1-node`.
|
||||
|
||||
#### zen
|
||||
|
||||
**Only** add if the issue mentions "zen" or "opencode zen" or "opencode black".
|
||||
|
||||
If the issue doesn't have "zen" or "opencode black" in it then don't add zen label
|
||||
|
||||
#### core
|
||||
|
||||
Use for core server issues in `packages/opencode/`, excluding `packages/opencode/src/cli/cmd/tui/`.
|
||||
|
||||
Examples:
|
||||
|
||||
- LSP server behavior
|
||||
- Harness behavior (agent + tools)
|
||||
- Feature requests for server behavior
|
||||
- Agent context construction
|
||||
- API endpoints
|
||||
- Provider integration issues
|
||||
- New, broken, or poor-quality models
|
||||
|
||||
#### acp
|
||||
|
||||
If the issue mentions acp support, assign acp label.
|
||||
|
||||
#### docs
|
||||
|
||||
Add if the issue requests better documentation or docs updates.
|
||||
|
||||
#### opentui
|
||||
|
||||
TUI issues potentially caused by our underlying TUI library:
|
||||
|
||||
- Keybindings not working
|
||||
- Scroll speed issues (too fast/slow/laggy)
|
||||
- Screen flickering
|
||||
- Crashes with opentui in the log
|
||||
|
||||
**Do not** add for general TUI bugs.
|
||||
|
||||
When assigning to people here are the following rules:
|
||||
|
||||
Desktop / Web:
|
||||
Use for desktop-labeled issues only.
|
||||
|
||||
- adamdotdevin
|
||||
- iamdavidhill
|
||||
- Brendonovich
|
||||
- nexxeln
|
||||
|
||||
Zen:
|
||||
ONLY assign if the issue will have the "zen" label.
|
||||
|
||||
- fwang
|
||||
- MrMushrooooom
|
||||
|
||||
TUI (`packages/opencode/src/cli/cmd/tui/...`):
|
||||
|
||||
- thdxr for TUI UX/UI product decisions and interaction flow
|
||||
- kommander for OpenTUI engine issues: rendering artifacts, keybind handling, terminal compatibility, SSH behavior, and low-level perf bottlenecks
|
||||
- rekram1-node for TUI bugs that are not clearly OpenTUI engine issues
|
||||
|
||||
Core (`packages/opencode/...`, excluding TUI subtree):
|
||||
|
||||
- thdxr for sqlite/snapshot/memory bugs and larger architectural core features
|
||||
- jlongster for opencode server + API feature work (tool currently remaps jlongster -> thdxr until assignable)
|
||||
- rekram1-node for harness issues, provider issues, and other bug-squashing
|
||||
|
||||
For core bugs that do not clearly map, either thdxr or rekram1-node is acceptable.
|
||||
|
||||
Docs:
|
||||
|
||||
- R44VC0RP
|
||||
|
||||
Windows:
|
||||
|
||||
- Hona (assign any issue that mentions Windows or is likely Windows-specific)
|
||||
|
||||
Determinism rules:
|
||||
|
||||
- If title + body does not contain "zen", do not add the "zen" label
|
||||
- If "nix" label is added but title + body does not mention nix/nixos, the tool will drop "nix"
|
||||
- If title + body mentions nix/nixos, assign to `rekram1-node`
|
||||
- If "desktop" label is added, the tool will override assignee and randomly pick one Desktop / Web owner
|
||||
|
||||
In all other cases, choose the team/section with the most overlap with the issue and assign a member from that team at random.
|
||||
|
||||
ACP:
|
||||
|
||||
- rekram1-node (assign any acp issues to rekram1-node)
|
||||
Windows-specific issues, including native Windows behavior, WSL interactions, path handling, shell compatibility, and installation or runtime problems that only happen on Windows.
|
||||
|
||||
@@ -18,9 +18,12 @@ Do not use `git log` or author metadata when deciding attribution.
|
||||
|
||||
Rules:
|
||||
|
||||
- Write the final file with sections in this order:
|
||||
- Write the final file with release sections in this order:
|
||||
`## Core`, `## TUI`, `## Desktop`, `## SDK`, `## Extensions`
|
||||
- Only include sections that have at least one notable entry
|
||||
- Within each release section, keep bug fixes grouped under `### Bugfixes`
|
||||
- Keep other notable entries under `### Improvements` when a section has bug fixes too
|
||||
- Omit empty subsections
|
||||
- Keep one bullet per commit you keep
|
||||
- Skip commits that are entirely internal, CI, tests, refactors, or otherwise not user-facing
|
||||
- Start each bullet with a capital letter
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
description: translate English to other languages
|
||||
model: opencode/claude-opus-4-7
|
||||
---
|
||||
|
||||
run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
||||
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
|
||||
- Also preserve every term listed in the Do-Not-Translate glossary below.
|
||||
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
||||
- Do not modify fenced code blocks.
|
||||
@@ -3,7 +3,7 @@
|
||||
"provider": {},
|
||||
"permission": {
|
||||
"edit": {
|
||||
"packages/opencode/migration/*": "deny",
|
||||
"packages/opencode/migration/*": "ask",
|
||||
},
|
||||
},
|
||||
"mcp": {},
|
||||
|
||||
+367
-285
@@ -1,35 +1,62 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import { RGBA, VignetteEffect } from "@opentui/core"
|
||||
import type {
|
||||
TuiKeybindSet,
|
||||
TuiPlugin,
|
||||
TuiPluginApi,
|
||||
TuiPluginMeta,
|
||||
TuiPluginModule,
|
||||
TuiSlotPlugin,
|
||||
} from "@opencode-ai/plugin/tui"
|
||||
import { useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import { useBindings, useKeymapSelector } from "@opentui/keymap/solid"
|
||||
import { RGBA, VignetteEffect, type KeyEvent, type Renderable } from "@opentui/core"
|
||||
import { createBindingLookup, type BindingConfig } from "@opentui/keymap/extras"
|
||||
import type { TuiPlugin, TuiPluginApi, TuiPluginMeta, TuiPluginModule, TuiSlotPlugin } from "@opencode-ai/plugin/tui"
|
||||
|
||||
const tabs = ["overview", "counter", "help"]
|
||||
const bind = {
|
||||
modal: "ctrl+shift+m",
|
||||
screen: "ctrl+shift+o",
|
||||
home: "escape,ctrl+h",
|
||||
left: "left,h",
|
||||
right: "right,l",
|
||||
up: "up,k",
|
||||
down: "down,j",
|
||||
alert: "a",
|
||||
confirm: "c",
|
||||
prompt: "p",
|
||||
select: "s",
|
||||
modal_accept: "enter,return",
|
||||
modal_close: "escape",
|
||||
dialog_close: "escape",
|
||||
local: "x",
|
||||
local_push: "enter,return",
|
||||
local_close: "q,backspace",
|
||||
host: "z",
|
||||
const command = {
|
||||
modal: "smoke_modal",
|
||||
screen: "smoke_screen",
|
||||
alert: "smoke_alert",
|
||||
confirm: "smoke_confirm",
|
||||
prompt: "smoke_prompt",
|
||||
select: "smoke_select",
|
||||
host: "smoke_host",
|
||||
home: "smoke_home",
|
||||
toast: "smoke_toast",
|
||||
dialog_close: "smoke_dialog_close",
|
||||
local_push: "smoke_local_push",
|
||||
local_pop: "smoke_local_pop",
|
||||
screen_home: "smoke_screen_home",
|
||||
screen_left: "smoke_screen_left",
|
||||
screen_right: "smoke_screen_right",
|
||||
screen_up: "smoke_screen_up",
|
||||
screen_down: "smoke_screen_down",
|
||||
screen_modal: "smoke_screen_modal",
|
||||
screen_local: "smoke_screen_local",
|
||||
screen_host: "smoke_screen_host",
|
||||
screen_alert: "smoke_screen_alert",
|
||||
screen_confirm: "smoke_screen_confirm",
|
||||
screen_prompt: "smoke_screen_prompt",
|
||||
screen_select: "smoke_screen_select",
|
||||
modal_accept: "smoke_modal_accept",
|
||||
modal_close: "smoke_modal_close",
|
||||
}
|
||||
|
||||
type SmokeBindings = BindingConfig<Renderable, KeyEvent>
|
||||
|
||||
const defaultKeymap = {
|
||||
[command.modal]: "ctrl+shift+m",
|
||||
[command.screen]: "ctrl+shift+o",
|
||||
[command.dialog_close]: "escape",
|
||||
[command.local_push]: "enter,return",
|
||||
[command.local_pop]: "escape,q,backspace",
|
||||
[command.screen_home]: "escape,ctrl+h",
|
||||
[command.screen_left]: "left,h",
|
||||
[command.screen_right]: "right,l",
|
||||
[command.screen_up]: "up,k",
|
||||
[command.screen_down]: "down,j",
|
||||
[command.screen_modal]: "ctrl+shift+m",
|
||||
[command.screen_local]: "x",
|
||||
[command.screen_host]: "z",
|
||||
[command.screen_alert]: "a",
|
||||
[command.screen_confirm]: "c",
|
||||
[command.screen_prompt]: "p",
|
||||
[command.screen_select]: "s",
|
||||
[command.modal_accept]: "enter,return",
|
||||
[command.modal_close]: "escape",
|
||||
}
|
||||
|
||||
const pick = (value: unknown, fallback: string) => {
|
||||
@@ -43,16 +70,14 @@ const num = (value: unknown, fallback: number) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const rec = (value: unknown) => {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return
|
||||
return Object.fromEntries(Object.entries(value))
|
||||
}
|
||||
const record = (value: unknown): value is Record<string, unknown> =>
|
||||
!!value && typeof value === "object" && !Array.isArray(value)
|
||||
|
||||
type Cfg = {
|
||||
label: string
|
||||
route: string
|
||||
vignette: number
|
||||
keybinds: Record<string, unknown> | undefined
|
||||
keybinds: SmokeBindings | undefined
|
||||
}
|
||||
|
||||
type Route = {
|
||||
@@ -74,7 +99,7 @@ const cfg = (options: Record<string, unknown> | undefined) => {
|
||||
label: pick(options?.label, "smoke"),
|
||||
route: pick(options?.route, "workspace-smoke"),
|
||||
vignette: Math.max(0, num(options?.vignette, 0.35)),
|
||||
keybinds: rec(options?.keybinds),
|
||||
keybinds: record(options?.keybinds) ? (options.keybinds as SmokeBindings) : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +110,12 @@ const names = (input: Cfg) => {
|
||||
}
|
||||
}
|
||||
|
||||
type Keys = TuiKeybindSet
|
||||
function createKeys(input: SmokeBindings | undefined) {
|
||||
return createBindingLookup({ ...defaultKeymap, ...input })
|
||||
}
|
||||
|
||||
type Keys = ReturnType<typeof createKeys>
|
||||
|
||||
const ui = {
|
||||
panel: "#1d1d1d",
|
||||
border: "#4a4a4a",
|
||||
@@ -292,125 +322,174 @@ const Screen = (props: {
|
||||
}
|
||||
const pop = (base?: State) => {
|
||||
const next = base ?? current(props.api, props.route)
|
||||
const local = Math.max(0, next.local - 1)
|
||||
set(local, next)
|
||||
set(Math.max(0, next.local - 1), next)
|
||||
}
|
||||
const show = () => {
|
||||
setTimeout(() => {
|
||||
open()
|
||||
}, 0)
|
||||
}
|
||||
useKeyboard((evt) => {
|
||||
if (props.api.route.current.name !== props.route.screen) return
|
||||
const next = current(props.api, props.route)
|
||||
if (props.api.ui.dialog.open) {
|
||||
if (props.keys.match("dialog_close", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.ui.dialog.clear()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
const screenActive = () => props.api.route.current.name === props.route.screen
|
||||
|
||||
if (next.local > 0) {
|
||||
if (evt.name === "escape" || props.keys.match("local_close", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
pop(next)
|
||||
return
|
||||
}
|
||||
useBindings(() => ({
|
||||
enabled: () => screenActive() && props.api.ui.dialog.open,
|
||||
commands: [
|
||||
{
|
||||
name: command.dialog_close,
|
||||
run() {
|
||||
props.api.ui.dialog.clear()
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: props.keys.gather("smoke.dialog", [command.dialog_close]),
|
||||
}))
|
||||
|
||||
if (props.keys.match("local_push", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
push(next)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
useBindings(() => ({
|
||||
enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local > 0,
|
||||
commands: [
|
||||
{
|
||||
name: command.local_push,
|
||||
run() {
|
||||
push(current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.local_pop,
|
||||
run() {
|
||||
pop(current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: props.keys.gather("smoke.local", [command.local_push, command.local_pop]),
|
||||
}))
|
||||
|
||||
if (props.keys.match("home", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate("home")
|
||||
return
|
||||
}
|
||||
useBindings(() => ({
|
||||
enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local === 0,
|
||||
commands: [
|
||||
{
|
||||
name: command.screen_home,
|
||||
run() {
|
||||
props.api.route.navigate("home")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_left,
|
||||
run() {
|
||||
const next = current(props.api, props.route)
|
||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length })
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_right,
|
||||
run() {
|
||||
const next = current(props.api, props.route)
|
||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length })
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_up,
|
||||
run() {
|
||||
const next = current(props.api, props.route)
|
||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 })
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_down,
|
||||
run() {
|
||||
const next = current(props.api, props.route)
|
||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 })
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_modal,
|
||||
run() {
|
||||
props.api.route.navigate(props.route.modal, current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_local,
|
||||
run() {
|
||||
open()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_host,
|
||||
run() {
|
||||
host(props.api, props.input, skin)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_alert,
|
||||
run() {
|
||||
warn(props.api, props.route, current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_confirm,
|
||||
run() {
|
||||
check(props.api, props.route, current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_prompt,
|
||||
run() {
|
||||
entry(props.api, props.route, current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.screen_select,
|
||||
run() {
|
||||
picker(props.api, props.route, current(props.api, props.route))
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: props.keys.gather("smoke.screen", [
|
||||
command.screen_home,
|
||||
command.screen_left,
|
||||
command.screen_right,
|
||||
command.screen_up,
|
||||
command.screen_down,
|
||||
command.screen_modal,
|
||||
command.screen_local,
|
||||
command.screen_host,
|
||||
command.screen_alert,
|
||||
command.screen_confirm,
|
||||
command.screen_prompt,
|
||||
command.screen_select,
|
||||
]),
|
||||
}))
|
||||
const shortcuts = useKeymapSelector((keymap) => {
|
||||
const bindings = keymap.getCommandBindings({
|
||||
visibility: "registered",
|
||||
commands: [
|
||||
command.screen_home,
|
||||
command.screen_up,
|
||||
command.screen_down,
|
||||
command.screen_modal,
|
||||
command.screen_alert,
|
||||
command.screen_confirm,
|
||||
command.screen_prompt,
|
||||
command.screen_select,
|
||||
command.screen_local,
|
||||
command.screen_host,
|
||||
command.local_push,
|
||||
command.local_pop,
|
||||
],
|
||||
})
|
||||
|
||||
if (props.keys.match("left", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length })
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("right", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length })
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("up", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 })
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("down", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 })
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("modal", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.modal, next)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("local", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
open()
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("host", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
host(props.api, props.input, skin)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("alert", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
warn(props.api, props.route, next)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("confirm", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
check(props.api, props.route, next)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("prompt", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
entry(props.api, props.route, next)
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("select", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
picker(props.api, props.route, next)
|
||||
return {
|
||||
screen_home: props.api.keys.formatBindings(bindings.get(command.screen_home)) ?? "",
|
||||
screen_up: props.api.keys.formatBindings(bindings.get(command.screen_up)) ?? "",
|
||||
screen_down: props.api.keys.formatBindings(bindings.get(command.screen_down)) ?? "",
|
||||
screen_modal: props.api.keys.formatBindings(bindings.get(command.screen_modal)) ?? "",
|
||||
screen_alert: props.api.keys.formatBindings(bindings.get(command.screen_alert)) ?? "",
|
||||
screen_confirm: props.api.keys.formatBindings(bindings.get(command.screen_confirm)) ?? "",
|
||||
screen_prompt: props.api.keys.formatBindings(bindings.get(command.screen_prompt)) ?? "",
|
||||
screen_select: props.api.keys.formatBindings(bindings.get(command.screen_select)) ?? "",
|
||||
screen_local: props.api.keys.formatBindings(bindings.get(command.screen_local)) ?? "",
|
||||
screen_host: props.api.keys.formatBindings(bindings.get(command.screen_host)) ?? "",
|
||||
local_push: props.api.keys.formatBindings(bindings.get(command.local_push)) ?? "",
|
||||
local_pop: props.api.keys.formatBindings(bindings.get(command.local_pop)) ?? "",
|
||||
}
|
||||
})
|
||||
|
||||
@@ -430,7 +509,7 @@ const Screen = (props: {
|
||||
<b>{props.input.label} screen</b>
|
||||
<span style={{ fg: skin.muted }}> plugin route</span>
|
||||
</text>
|
||||
<text fg={skin.muted}>{props.keys.print("home")} home</text>
|
||||
<text fg={skin.muted}>{shortcuts().screen_home} home</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1} paddingBottom={1}>
|
||||
@@ -477,7 +556,7 @@ const Screen = (props: {
|
||||
<box flexDirection="column" gap={1}>
|
||||
<text fg={skin.text}>Counter: {value.count}</text>
|
||||
<text fg={skin.muted}>
|
||||
{props.keys.print("up")} / {props.keys.print("down")} change value
|
||||
{shortcuts().screen_up} / {shortcuts().screen_down} change value
|
||||
</text>
|
||||
</box>
|
||||
) : null}
|
||||
@@ -485,17 +564,16 @@ const Screen = (props: {
|
||||
{value.tab === 2 ? (
|
||||
<box flexDirection="column" gap={1}>
|
||||
<text fg={skin.muted}>
|
||||
{props.keys.print("modal")} modal | {props.keys.print("alert")} alert | {props.keys.print("confirm")}{" "}
|
||||
confirm | {props.keys.print("prompt")} prompt | {props.keys.print("select")} select
|
||||
{shortcuts().screen_modal} modal | {shortcuts().screen_alert} alert | {shortcuts().screen_confirm}{" "}
|
||||
confirm | {shortcuts().screen_prompt} prompt | {shortcuts().screen_select} select
|
||||
</text>
|
||||
<text fg={skin.muted}>
|
||||
{props.keys.print("local")} local stack | {props.keys.print("host")} host stack
|
||||
{shortcuts().screen_local} local stack | {shortcuts().screen_host} host stack
|
||||
</text>
|
||||
<text fg={skin.muted}>
|
||||
local open: {props.keys.print("local_push")} push nested · esc or {props.keys.print("local_close")}{" "}
|
||||
close
|
||||
local open: {shortcuts().local_push} push nested · {shortcuts().local_pop} close
|
||||
</text>
|
||||
<text fg={skin.muted}>{props.keys.print("home")} returns home</text>
|
||||
<text fg={skin.muted}>{shortcuts().screen_home} returns home</text>
|
||||
</box>
|
||||
) : null}
|
||||
</box>
|
||||
@@ -548,7 +626,7 @@ const Screen = (props: {
|
||||
</text>
|
||||
<text fg={skin.muted}>Plugin-owned stack depth: {value.local}</text>
|
||||
<text fg={skin.muted}>
|
||||
{props.keys.print("local_push")} push nested · {props.keys.print("local_close")} pop/close
|
||||
{shortcuts().local_push} push nested · {shortcuts().local_pop} pop/close
|
||||
</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<Btn txt="push" run={push} skin={skin} on />
|
||||
@@ -571,20 +649,35 @@ const Modal = (props: {
|
||||
const value = parse(props.params)
|
||||
const skin = tone(props.api)
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (props.api.route.current.name !== props.route.modal) return
|
||||
useBindings(() => ({
|
||||
enabled: () => props.api.route.current.name === props.route.modal,
|
||||
commands: [
|
||||
{
|
||||
name: command.modal_accept,
|
||||
run() {
|
||||
props.api.route.navigate(props.route.screen, { ...parse(props.params), source: "modal" })
|
||||
},
|
||||
},
|
||||
{
|
||||
name: command.modal_close,
|
||||
run() {
|
||||
props.api.route.navigate("home")
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: props.keys.gather("smoke.modal", [command.modal_accept, command.modal_close]),
|
||||
}))
|
||||
const shortcuts = useKeymapSelector((keymap) => {
|
||||
const bindings = keymap.getCommandBindings({
|
||||
visibility: "registered",
|
||||
commands: [command.modal, command.screen, command.modal_accept, command.modal_close],
|
||||
})
|
||||
|
||||
if (props.keys.match("modal_accept", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate(props.route.screen, { ...value, source: "modal" })
|
||||
return
|
||||
}
|
||||
|
||||
if (props.keys.match("modal_close", evt)) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
props.api.route.navigate("home")
|
||||
return {
|
||||
modal: props.api.keys.formatBindings(bindings.get(command.modal)) ?? "",
|
||||
screen: props.api.keys.formatBindings(bindings.get(command.screen)) ?? "",
|
||||
modal_accept: props.api.keys.formatBindings(bindings.get(command.modal_accept)) ?? "",
|
||||
modal_close: props.api.keys.formatBindings(bindings.get(command.modal_close)) ?? "",
|
||||
}
|
||||
})
|
||||
|
||||
@@ -595,10 +688,10 @@ const Modal = (props: {
|
||||
<text fg={skin.text}>
|
||||
<b>{props.input.label} modal</b>
|
||||
</text>
|
||||
<text fg={skin.muted}>{props.keys.print("modal")} modal command</text>
|
||||
<text fg={skin.muted}>{props.keys.print("screen")} screen command</text>
|
||||
<text fg={skin.muted}>{shortcuts().modal} modal command</text>
|
||||
<text fg={skin.muted}>{shortcuts().screen} screen command</text>
|
||||
<text fg={skin.muted}>
|
||||
{props.keys.print("modal_accept")} opens screen · {props.keys.print("modal_close")} closes
|
||||
{shortcuts().modal_accept} opens screen · {shortcuts().modal_close} closes
|
||||
</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<Btn
|
||||
@@ -651,25 +744,8 @@ const home = (api: TuiPluginApi, input: Cfg) => ({
|
||||
},
|
||||
home_prompt(ctx, value) {
|
||||
const skin = look(ctx.theme.current)
|
||||
type Prompt = (props: {
|
||||
workspaceID?: string
|
||||
visible?: boolean
|
||||
disabled?: boolean
|
||||
onSubmit?: () => void
|
||||
hint?: JSX.Element
|
||||
right?: JSX.Element
|
||||
showPlaceholder?: boolean
|
||||
placeholders?: {
|
||||
normal?: string[]
|
||||
shell?: string[]
|
||||
}
|
||||
}) => JSX.Element
|
||||
type Slot = (
|
||||
props: { name: string; mode?: unknown; children?: JSX.Element } & Record<string, unknown>,
|
||||
) => JSX.Element | null
|
||||
const ui = api.ui as TuiPluginApi["ui"] & { Prompt: Prompt; Slot: Slot }
|
||||
const Prompt = ui.Prompt
|
||||
const Slot = ui.Slot
|
||||
const Prompt = api.ui.Prompt
|
||||
const Slot = api.ui.Slot
|
||||
const normal = [
|
||||
`[SMOKE] route check for ${input.label}`,
|
||||
"[SMOKE] confirm home_prompt slot override",
|
||||
@@ -791,109 +867,115 @@ const slot = (api: TuiPluginApi, input: Cfg): TuiSlotPlugin[] => [
|
||||
|
||||
const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
|
||||
const route = names(input)
|
||||
api.command.register(() => [
|
||||
{
|
||||
title: `${input.label} modal`,
|
||||
value: "plugin.smoke.modal",
|
||||
keybind: keys.get("modal"),
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke",
|
||||
api.keymap.registerLayer({
|
||||
commands: [
|
||||
{
|
||||
name: command.modal,
|
||||
title: `${input.label} modal`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke",
|
||||
run() {
|
||||
api.route.navigate(route.modal, { source: "command" })
|
||||
},
|
||||
},
|
||||
onSelect: () => {
|
||||
api.route.navigate(route.modal, { source: "command" })
|
||||
{
|
||||
name: command.screen,
|
||||
title: `${input.label} screen`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-screen",
|
||||
run() {
|
||||
api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 })
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} screen`,
|
||||
value: "plugin.smoke.screen",
|
||||
keybind: keys.get("screen"),
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-screen",
|
||||
{
|
||||
name: command.alert,
|
||||
title: `${input.label} alert dialog`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-alert",
|
||||
run() {
|
||||
warn(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
onSelect: () => {
|
||||
api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 })
|
||||
{
|
||||
name: command.confirm,
|
||||
title: `${input.label} confirm dialog`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-confirm",
|
||||
run() {
|
||||
check(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} alert dialog`,
|
||||
value: "plugin.smoke.alert",
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-alert",
|
||||
{
|
||||
name: command.prompt,
|
||||
title: `${input.label} prompt dialog`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-prompt",
|
||||
run() {
|
||||
entry(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
onSelect: () => {
|
||||
warn(api, route, current(api, route))
|
||||
{
|
||||
name: command.select,
|
||||
title: `${input.label} select dialog`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-select",
|
||||
run() {
|
||||
picker(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} confirm dialog`,
|
||||
value: "plugin.smoke.confirm",
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-confirm",
|
||||
{
|
||||
name: command.host,
|
||||
title: `${input.label} host overlay`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "smoke-host",
|
||||
run() {
|
||||
host(api, input, tone(api))
|
||||
},
|
||||
},
|
||||
onSelect: () => {
|
||||
check(api, route, current(api, route))
|
||||
{
|
||||
name: command.home,
|
||||
title: `${input.label} go home`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
enabled: () => api.route.current.name !== "home",
|
||||
run() {
|
||||
api.route.navigate("home")
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} prompt dialog`,
|
||||
value: "plugin.smoke.prompt",
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-prompt",
|
||||
{
|
||||
name: command.toast,
|
||||
title: `${input.label} toast`,
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
run() {
|
||||
api.ui.toast({
|
||||
variant: "info",
|
||||
title: "Smoke",
|
||||
message: "Plugin toast works",
|
||||
duration: 2000,
|
||||
})
|
||||
},
|
||||
},
|
||||
onSelect: () => {
|
||||
entry(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} select dialog`,
|
||||
value: "plugin.smoke.select",
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-select",
|
||||
},
|
||||
onSelect: () => {
|
||||
picker(api, route, current(api, route))
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} host overlay`,
|
||||
value: "plugin.smoke.host",
|
||||
category: "Plugin",
|
||||
slash: {
|
||||
name: "smoke-host",
|
||||
},
|
||||
onSelect: () => {
|
||||
host(api, input, tone(api))
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} go home`,
|
||||
value: "plugin.smoke.home",
|
||||
category: "Plugin",
|
||||
enabled: api.route.current.name !== "home",
|
||||
onSelect: () => {
|
||||
api.route.navigate("home")
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `${input.label} toast`,
|
||||
value: "plugin.smoke.toast",
|
||||
category: "Plugin",
|
||||
onSelect: () => {
|
||||
api.ui.toast({
|
||||
variant: "info",
|
||||
title: "Smoke",
|
||||
message: "Plugin toast works",
|
||||
duration: 2000,
|
||||
})
|
||||
},
|
||||
},
|
||||
])
|
||||
],
|
||||
bindings: keys.gather("smoke.global", [
|
||||
command.modal,
|
||||
command.screen,
|
||||
command.alert,
|
||||
command.confirm,
|
||||
command.prompt,
|
||||
command.select,
|
||||
command.host,
|
||||
command.home,
|
||||
command.toast,
|
||||
]),
|
||||
})
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api, options, meta) => {
|
||||
@@ -902,9 +984,9 @@ const tui: TuiPlugin = async (api, options, meta) => {
|
||||
await api.theme.install("./smoke-theme.json")
|
||||
api.theme.set("smoke-theme")
|
||||
|
||||
const value = cfg(options ?? undefined)
|
||||
const value = cfg(options)
|
||||
const route = names(value)
|
||||
const keys = api.keybind.create(bind, value.keybinds)
|
||||
const keys = createKeys(value.keybinds)
|
||||
const fx = new VignetteEffect(value.vignette)
|
||||
const post = fx.apply.bind(fx)
|
||||
api.renderer.addPostProcessFn(post)
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
---
|
||||
name: effect
|
||||
description: Answer questions about the Effect framework
|
||||
description: Work with Effect v4 / effect-smol TypeScript code in this repo
|
||||
---
|
||||
|
||||
# Effect
|
||||
|
||||
This codebase uses Effect, a framework for writing typescript.
|
||||
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
|
||||
|
||||
## How to Answer Effect Questions
|
||||
## Source Of Truth
|
||||
|
||||
1. Clone the Effect repository: `https://github.com/Effect-TS/effect-smol` to
|
||||
`.opencode/references/effect-smol` in this project NOT the skill folder.
|
||||
2. Use the explore agent to search the codebase for answers about Effect patterns, APIs, and concepts
|
||||
3. Provide responses based on the actual Effect source code and documentation
|
||||
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
|
||||
|
||||
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
|
||||
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
|
||||
3. Also inspect existing repo code for local house style before introducing new patterns.
|
||||
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Always use the explore agent with the cloned repository when answering Effect-related questions
|
||||
- Reference specific files and patterns found in the Effect codebase
|
||||
- Do not answer from memory - always verify against the source
|
||||
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
|
||||
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
|
||||
- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows.
|
||||
- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces.
|
||||
- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services.
|
||||
- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so.
|
||||
- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see.
|
||||
- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior.
|
||||
- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types.
|
||||
- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first.
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
- Use `testEffect(...)` from `packages/opencode/test/lib/effect.ts` for tests that exercise Effect services, layers, runtime context, scoped resources, or platform integrations.
|
||||
- Use `it.live(...)` for filesystem, git repositories, HTTP servers, sockets, child processes, locks, real time, and other live platform behavior.
|
||||
- Run tests from package directories such as `packages/opencode`; never run package tests from the repo root.
|
||||
- Prefer explicit test layers over ad hoc managed runtimes. Keep dependency provisioning visible in the test file.
|
||||
- Use scoped fixtures and finalizers for resources that must be cleaned up, including temporary directories, flags, databases, fibers, servers, and global state.
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
/// <reference path="../env.d.ts" />
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
|
||||
const TEAM = {
|
||||
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
|
||||
zen: ["fwang", "MrMushrooooom"],
|
||||
tui: ["thdxr", "kommander", "rekram1-node"],
|
||||
core: ["thdxr", "rekram1-node", "jlongster"],
|
||||
docs: ["R44VC0RP"],
|
||||
tui: ["kommander", "simonklee"],
|
||||
desktop_web: ["Hona", "Brendonovich"],
|
||||
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton"],
|
||||
inference: ["fwang", "MrMushrooooom"],
|
||||
windows: ["Hona"],
|
||||
} as const
|
||||
|
||||
const ASSIGNEES = [...new Set(Object.values(TEAM).flat())]
|
||||
|
||||
function pick<T>(items: readonly T[]) {
|
||||
return items[Math.floor(Math.random() * items.length)]!
|
||||
}
|
||||
@@ -38,79 +36,25 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
}
|
||||
|
||||
export default tool({
|
||||
description: `Use this tool to assign and/or label a GitHub issue.
|
||||
description: `Use this tool to assign a GitHub issue.
|
||||
|
||||
Choose labels and assignee using the current triage policy and ownership rules.
|
||||
Pick the most fitting labels for the issue and assign one owner.
|
||||
|
||||
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.`,
|
||||
Provide the team that should own the issue. This tool picks a random assignee from that team and does not apply labels.`,
|
||||
args: {
|
||||
assignee: tool.schema
|
||||
.enum(ASSIGNEES as [string, ...string[]])
|
||||
.describe("The username of the assignee")
|
||||
.default("rekram1-node"),
|
||||
labels: tool.schema
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"]))
|
||||
.describe("The labels(s) to add to the issue")
|
||||
.default([]),
|
||||
team: tool.schema
|
||||
.enum(Object.keys(TEAM) as [keyof typeof TEAM, ...(keyof typeof TEAM)[]])
|
||||
.describe("The owning team"),
|
||||
},
|
||||
async execute(args) {
|
||||
const issue = getIssueNumber()
|
||||
const owner = "anomalyco"
|
||||
const repo = "opencode"
|
||||
|
||||
const results: string[] = []
|
||||
let labels = [...new Set(args.labels.map((x) => (x === "desktop" ? "web" : x)))]
|
||||
const web = labels.includes("web")
|
||||
const text = `${process.env.ISSUE_TITLE ?? ""}\n${process.env.ISSUE_BODY ?? ""}`.toLowerCase()
|
||||
const zen = /\bzen\b/.test(text) || text.includes("opencode black")
|
||||
const nix = /\bnix(os)?\b/.test(text)
|
||||
|
||||
if (labels.includes("nix") && !nix) {
|
||||
labels = labels.filter((x) => x !== "nix")
|
||||
results.push("Dropped label: nix (issue does not mention nix)")
|
||||
}
|
||||
|
||||
const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
|
||||
|
||||
if (labels.includes("zen") && !zen) {
|
||||
throw new Error("Only add the zen label when issue title/body contains 'zen'")
|
||||
}
|
||||
|
||||
if (web && !nix && !(TEAM.desktop as readonly string[]).includes(assignee)) {
|
||||
throw new Error("Web issues must be assigned to adamdotdevin, iamdavidhill, Brendonovich, or nexxeln")
|
||||
}
|
||||
|
||||
if ((TEAM.zen as readonly string[]).includes(assignee) && !labels.includes("zen")) {
|
||||
throw new Error("Only zen issues should be assigned to fwang or MrMushrooooom")
|
||||
}
|
||||
|
||||
if (assignee === "Hona" && !labels.includes("windows")) {
|
||||
throw new Error("Only windows issues should be assigned to Hona")
|
||||
}
|
||||
|
||||
if (assignee === "R44VC0RP" && !labels.includes("docs")) {
|
||||
throw new Error("Only docs issues should be assigned to R44VC0RP")
|
||||
}
|
||||
|
||||
if (assignee === "kommander" && !labels.includes("opentui")) {
|
||||
throw new Error("Only opentui issues should be assigned to kommander")
|
||||
}
|
||||
const assignee = pick(TEAM[args.team])
|
||||
|
||||
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ assignees: [assignee] }),
|
||||
})
|
||||
results.push(`Assigned @${assignee} to issue #${issue}`)
|
||||
|
||||
if (labels.length > 0) {
|
||||
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ labels }),
|
||||
})
|
||||
results.push(`Added labels: ${labels.join(", ")}`)
|
||||
}
|
||||
|
||||
return results.join("\n")
|
||||
return `Assigned @${assignee} from ${args.team} to issue #${issue}`
|
||||
},
|
||||
})
|
||||
|
||||
+5
-4
@@ -7,10 +7,11 @@
|
||||
"enabled": false,
|
||||
"label": "workspace",
|
||||
"keybinds": {
|
||||
"modal": "ctrl+alt+m",
|
||||
"screen": "ctrl+alt+o",
|
||||
"home": "escape,ctrl+shift+h",
|
||||
"dialog_close": "escape,q"
|
||||
"smoke_modal": "ctrl+alt+m",
|
||||
"smoke_screen": "ctrl+alt+o",
|
||||
"smoke_screen_home": "escape,ctrl+shift+h",
|
||||
"smoke_screen_modal": "ctrl+alt+m",
|
||||
"smoke_dialog_close": "escape,q"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
### General Principles
|
||||
|
||||
- Keep things in one function unless composable or reusable
|
||||
- Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
|
||||
- Avoid `try`/`catch` where possible
|
||||
- Avoid using the `any` type
|
||||
- Use Bun APIs when possible, like `Bun.file()`
|
||||
|
||||
+6
-18
@@ -73,7 +73,7 @@ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
|
||||
- `packages/opencode`: OpenCode core business logic & server.
|
||||
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
||||
- `packages/app`: The shared web UI components, written in SolidJS
|
||||
- `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`)
|
||||
- `packages/desktop`: The native desktop app, built with Electron (wraps `packages/app`)
|
||||
- `packages/plugin`: Source for `@opencode-ai/plugin`
|
||||
|
||||
### Understanding bun dev vs opencode
|
||||
@@ -123,33 +123,21 @@ This starts a local dev server at http://localhost:5173 (or similar port shown i
|
||||
|
||||
### Running the Desktop App
|
||||
|
||||
The desktop app is a native Tauri application that wraps the web UI.
|
||||
The desktop app is an Electron application that wraps the web UI.
|
||||
|
||||
To run the native desktop app:
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop tauri dev
|
||||
```
|
||||
|
||||
This starts the web dev server on http://localhost:1420 and opens the native window.
|
||||
|
||||
If you only want the web dev server (no native shell):
|
||||
To run the desktop app in development:
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop dev
|
||||
```
|
||||
|
||||
To create a production `dist/` and build the native app bundle:
|
||||
To create a production build and package the app:
|
||||
|
||||
```bash
|
||||
bun run --cwd packages/desktop tauri build
|
||||
bun run --cwd packages/desktop build
|
||||
bun run --cwd packages/desktop package
|
||||
```
|
||||
|
||||
This runs `bun run --cwd packages/desktop build` automatically via Tauri’s `beforeBuildCommand`.
|
||||
|
||||
> [!NOTE]
|
||||
> Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
||||
|
||||
> [!NOTE]
|
||||
> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
|
||||
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # او github:anomalyco/opencode لاحدث
|
||||
|
||||
يتوفر OpenCode ايضا كتطبيق سطح مكتب. قم بالتنزيل مباشرة من [صفحة الاصدارات](https://github.com/anomalyco/opencode/releases) او من [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| المنصة | التنزيل |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb` او `.rpm` او AppImage |
|
||||
| المنصة | التنزيل |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb` او `.rpm` او AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev
|
||||
|
||||
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
|
||||
|
||||
| প্ল্যাটফর্ম | ডাউনলোড |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
||||
| প্ল্যাটফর্ম | ডাউনলোড |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or `.AppImage` |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # ou github:anomalyco/opencode para a branch
|
||||
|
||||
O OpenCode também está disponível como aplicativo desktop. Baixe diretamente pela [página de releases](https://github.com/anomalyco/opencode/releases) ou em [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Plataforma | Download |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` ou AppImage |
|
||||
| Plataforma | Download |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` ou AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # ili github:anomalyco/opencode za najnoviji
|
||||
|
||||
OpenCode je dostupan i kao desktop aplikacija. Preuzmi je direktno sa [stranice izdanja](https://github.com/anomalyco/opencode/releases) ili sa [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Platforma | Preuzimanje |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ili AppImage |
|
||||
| Platforma | Preuzimanje |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ili AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste
|
||||
|
||||
OpenCode findes også som desktop-app. Download direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Platform | Download |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, eller AppImage |
|
||||
| Platform | Download |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, eller AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # oder github:anomalyco/opencode für den neu
|
||||
|
||||
OpenCode ist auch als Desktop-Anwendung verfügbar. Lade sie direkt von der [Releases-Seite](https://github.com/anomalyco/opencode/releases) oder [opencode.ai/download](https://opencode.ai/download) herunter.
|
||||
|
||||
| Plattform | Download |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` oder AppImage |
|
||||
| Plattform | Download |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` oder AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # o github:anomalyco/opencode para la rama de
|
||||
|
||||
OpenCode también está disponible como aplicación de escritorio. Descárgala directamente desde la [página de releases](https://github.com/anomalyco/opencode/releases) o desde [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Plataforma | Descarga |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, o AppImage |
|
||||
| Plataforma | Descarga |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, o AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # ou github:anomalyco/opencode pour la branch
|
||||
|
||||
OpenCode est aussi disponible en application de bureau. Téléchargez-la directement depuis la [page des releases](https://github.com/anomalyco/opencode/releases) ou [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Plateforme | Téléchargement |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ou AppImage |
|
||||
| Plateforme | Téléchargement |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ou AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # ή github:anomalyco/opencode με βάση
|
||||
|
||||
Το OpenCode είναι επίσης διαθέσιμο ως εφαρμογή. Κατέβασε το απευθείας από τη [σελίδα εκδόσεων](https://github.com/anomalyco/opencode/releases) ή το [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Πλατφόρμα | Λήψη |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ή AppImage |
|
||||
| Πλατφόρμα | Λήψη |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, ή AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # oppure github:anomalyco/opencode per l’ul
|
||||
|
||||
OpenCode è disponibile anche come applicazione desktop. Puoi scaricarla direttamente dalla [pagina delle release](https://github.com/anomalyco/opencode/releases) oppure da [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Piattaforma | Download |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, oppure AppImage |
|
||||
| Piattaforma | Download |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, oppure AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # または github:anomalyco/opencode で最
|
||||
|
||||
OpenCode はデスクトップアプリとしても利用できます。[releases page](https://github.com/anomalyco/opencode/releases) から直接ダウンロードするか、[opencode.ai/download](https://opencode.ai/download) を利用してください。
|
||||
|
||||
| プラットフォーム | ダウンロード |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`、`.rpm`、または AppImage |
|
||||
| プラットフォーム | ダウンロード |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`、`.rpm`、または AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # 또는 github:anomalyco/opencode 로 최신
|
||||
|
||||
OpenCode 는 데스크톱 앱으로도 제공됩니다. [releases page](https://github.com/anomalyco/opencode/releases) 에서 직접 다운로드하거나 [opencode.ai/download](https://opencode.ai/download) 를 이용하세요.
|
||||
|
||||
| 플랫폼 | 다운로드 |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, 또는 AppImage |
|
||||
| 플랫폼 | 다운로드 |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, 또는 AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev
|
||||
|
||||
OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Platform | Download |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
||||
| Platform | Download |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or `.AppImage` |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
@@ -132,7 +132,7 @@ It's very similar to Claude Code in terms of capability. Here are the key differ
|
||||
|
||||
- 100% open source
|
||||
- Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen), OpenCode can be used with Claude, OpenAI, Google, or even local models. As models evolve, the gaps between them will close and pricing will drop, so being provider-agnostic is important.
|
||||
- Out-of-the-box LSP support
|
||||
- Built-in opt-in LSP support
|
||||
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This, for example, can allow OpenCode to run on your computer while you drive it remotely from a mobile app, meaning that the TUI frontend is just one of the possible clients.
|
||||
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste
|
||||
|
||||
OpenCode er også tilgjengelig som en desktop-app. Last ned direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Plattform | Nedlasting |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` eller AppImage |
|
||||
| Plattform | Nedlasting |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` eller AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # lub github:anomalyco/opencode dla najnowsze
|
||||
|
||||
OpenCode jest także dostępny jako aplikacja desktopowa. Pobierz ją bezpośrednio ze strony [releases](https://github.com/anomalyco/opencode/releases) lub z [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Platforma | Pobieranie |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` lub AppImage |
|
||||
| Platforma | Pobieranie |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` lub AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # или github:anomalyco/opencode для с
|
||||
|
||||
OpenCode также доступен как десктопное приложение. Скачайте его со [страницы релизов](https://github.com/anomalyco/opencode/releases) или с [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Платформа | Загрузка |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` или AppImage |
|
||||
| Платформа | Загрузка |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` или AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # หรือ github:anomalyco/opencode ส
|
||||
|
||||
OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/opencode/releases) หรือ [opencode.ai/download](https://opencode.ai/download)
|
||||
|
||||
| แพลตฟอร์ม | ดาวน์โหลด |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, หรือ AppImage |
|
||||
| แพลตฟอร์ม | ดาวน์โหลด |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, หรือ AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # veya en güncel geliştirme dalı için git
|
||||
|
||||
OpenCode ayrıca masaüstü uygulaması olarak da mevcuttur. Doğrudan [sürüm sayfasından](https://github.com/anomalyco/opencode/releases) veya [opencode.ai/download](https://opencode.ai/download) adresinden indirebilirsiniz.
|
||||
|
||||
| Platform | İndirme |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` veya AppImage |
|
||||
| Platform | İndirme |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` veya AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # або github:anomalyco/opencode для н
|
||||
|
||||
OpenCode також доступний як десктопний застосунок. Завантажуйте напряму зі [сторінки релізів](https://github.com/anomalyco/opencode/releases) або [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Платформа | Завантаження |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` або AppImage |
|
||||
| Платформа | Завантаження |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm` або AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # hoặc github:anomalyco/opencode cho nhánh
|
||||
|
||||
OpenCode cũng có sẵn dưới dạng ứng dụng desktop. Tải trực tiếp từ [trang releases](https://github.com/anomalyco/opencode/releases) hoặc [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Nền tảng | Tải xuống |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, hoặc AppImage |
|
||||
| Nền tảng | Tải xuống |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, hoặc AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最
|
||||
|
||||
OpenCode 也提供桌面版应用。可直接从 [发布页 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下载。
|
||||
|
||||
| 平台 | 下载文件 |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`、`.rpm` 或 AppImage |
|
||||
| 平台 | 下载文件 |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`、`.rpm` 或 AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew Cask)
|
||||
|
||||
+6
-6
@@ -68,12 +68,12 @@ nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取
|
||||
|
||||
OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。
|
||||
|
||||
| 平台 | 下載連結 |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, 或 AppImage |
|
||||
| 平台 | 下載連結 |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, 或 AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew Cask)
|
||||
|
||||
@@ -30,6 +30,7 @@ export const api = new sst.cloudflare.Worker("Api", {
|
||||
transform: {
|
||||
worker: (args) => {
|
||||
args.logpush = true
|
||||
if ($app.stage === "vimtor") return
|
||||
args.bindings = $resolve(args.bindings).apply((bindings) => [
|
||||
...bindings,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { domain } from "./stage"
|
||||
import { EMAILOCTOPUS_API_KEY } from "./app"
|
||||
import { SECRET } from "./secret"
|
||||
|
||||
////////////////
|
||||
// DATABASE
|
||||
@@ -115,6 +116,27 @@ const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "once",
|
||||
})
|
||||
const zenLiteCouponThreeMonths100 = new stripe.Coupon("ZenLiteCoupon3Months100", {
|
||||
name: "3 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 3,
|
||||
})
|
||||
const zenLiteCouponSixMonths100 = new stripe.Coupon("ZenLiteCoupon6Months100", {
|
||||
name: "6 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 6,
|
||||
})
|
||||
const zenLiteCouponTwelveMonths100 = new stripe.Coupon("ZenLiteCoupon12Months100", {
|
||||
name: "12 months 100% off",
|
||||
percentOff: 100,
|
||||
appliesToProducts: [zenLiteProduct.id],
|
||||
duration: "repeating",
|
||||
durationInMonths: 12,
|
||||
})
|
||||
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
||||
product: zenLiteProduct.id,
|
||||
currency: "usd",
|
||||
@@ -131,6 +153,9 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
|
||||
priceInr: 92900,
|
||||
firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
|
||||
firstMonth100Coupon: zenLiteCouponFirstMonth100.id,
|
||||
threeMonths100Coupon: zenLiteCouponThreeMonths100.id,
|
||||
sixMonths100Coupon: zenLiteCouponSixMonths100.id,
|
||||
twelveMonths100Coupon: zenLiteCouponTwelveMonths100.id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -197,6 +222,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
||||
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||
properties: { value: stripeWebhook.secret },
|
||||
})
|
||||
|
||||
const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
||||
|
||||
////////////////
|
||||
@@ -206,6 +232,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
||||
const bucket = new sst.cloudflare.Bucket("ZenData")
|
||||
const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
|
||||
|
||||
const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL")
|
||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||
|
||||
@@ -227,6 +254,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||
database,
|
||||
AUTH_API_URL,
|
||||
STRIPE_WEBHOOK_SECRET,
|
||||
DISCORD_INCIDENT_WEBHOOK_URL,
|
||||
SECRET.HoneycombWebhookSecret,
|
||||
STRIPE_SECRET_KEY,
|
||||
EMAILOCTOPUS_API_KEY,
|
||||
AWS_SES_ACCESS_KEY_ID,
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { SECRET } from "./secret"
|
||||
import { domain } from "./stage"
|
||||
|
||||
const description = "Managed by SST (Don't edit in Honeycomb UI)"
|
||||
|
||||
const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", {
|
||||
name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
|
||||
url: `https://${domain}/honeycomb/webhook`,
|
||||
secret: SECRET.HoneycombWebhookSecret.result,
|
||||
templates: [
|
||||
{
|
||||
type: "trigger",
|
||||
body: `{
|
||||
"url": {{ .Result.URL | quote }},
|
||||
"type": {{ .Vars.type | quote }},
|
||||
"name": {{ .Name | quote }},
|
||||
"status": {{ .Alert.Status | quote }},
|
||||
"isTest": {{ .Alert.IsTest }},
|
||||
"groups": {{ .Result.GroupsTriggered | toJson }}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
variables: [
|
||||
{
|
||||
name: "type",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Honeycomb can keep stale query-local calculated fields when the name is unchanged,
|
||||
// so tie the field name to the expression while avoiding deploy-to-deploy churn.
|
||||
// https://github.com/honeycombio/terraform-provider-honeycombio/issues/852
|
||||
const calculatedField = (field: { name: string; expression: string }) => ({
|
||||
...field,
|
||||
name: `${field.name}_${(
|
||||
Array.from(field.expression).reduce((result, char) => Math.imul(31, result) + char.charCodeAt(0), 0) >>> 0
|
||||
).toString(36)}`,
|
||||
})
|
||||
|
||||
const modelHttpErrorsQuery = (product: "go" | "zen") => {
|
||||
const filters = [
|
||||
{ column: "model", op: "exists" },
|
||||
{ column: "event_type", op: "=", value: "completions" },
|
||||
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
|
||||
]
|
||||
const failedHttpStatus = calculatedField({
|
||||
name: "is_failed_http_status",
|
||||
expression:
|
||||
product === "go"
|
||||
? `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401")), NOT(EQUALS($status, "429"))), 1, 0)`
|
||||
: `IF(AND(EQUALS($status, "429"), $isFreeTier), 0, AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`,
|
||||
})
|
||||
|
||||
return honeycomb.getQuerySpecificationOutput({
|
||||
breakdowns: ["model"],
|
||||
calculatedFields: [failedHttpStatus],
|
||||
calculations: [
|
||||
{ op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
|
||||
{
|
||||
op: "SUM",
|
||||
name: "FAILED",
|
||||
column: failedHttpStatus.name,
|
||||
filterCombination: "AND",
|
||||
filters,
|
||||
},
|
||||
],
|
||||
formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 100), DIV($FAILED, $TOTAL), 0)" }],
|
||||
timeRange: 900,
|
||||
}).json
|
||||
}
|
||||
|
||||
const providerHttpErrorsQuery = (product: "go" | "zen") => {
|
||||
const filters = [
|
||||
{ column: "provider", op: "exists" },
|
||||
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
|
||||
]
|
||||
const successHttpStatus = calculatedField({
|
||||
name: "is_success_http_status",
|
||||
expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`,
|
||||
})
|
||||
const failedProviderHttpStatus = calculatedField({
|
||||
name: "is_failed_provider_http_status",
|
||||
expression: `IF(GT($llm.error.code, "400"), 1, 0)`,
|
||||
})
|
||||
|
||||
return honeycomb.getQuerySpecificationOutput({
|
||||
breakdowns: ["provider"],
|
||||
calculatedFields: [successHttpStatus, failedProviderHttpStatus],
|
||||
calculations: [
|
||||
{
|
||||
op: "SUM",
|
||||
name: "SUCCESS",
|
||||
column: successHttpStatus.name,
|
||||
filterCombination: "AND",
|
||||
filters: [...filters, { column: "event_type", op: "=", value: "completions" }],
|
||||
},
|
||||
{
|
||||
op: "SUM",
|
||||
name: "FAILED",
|
||||
column: failedProviderHttpStatus.name,
|
||||
filterCombination: "AND",
|
||||
filters: [...filters, { column: "event_type", op: "=", value: "llm.error" }],
|
||||
},
|
||||
],
|
||||
formulas: [
|
||||
{ name: "ERROR", expression: "IF(GTE(SUM($SUCCESS, $FAILED), 50), DIV($FAILED, SUM($SUCCESS, $FAILED)), 0)" },
|
||||
],
|
||||
timeRange: 900,
|
||||
}).json
|
||||
}
|
||||
|
||||
new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
|
||||
name: "Increased Model HTTP Errors [Go]",
|
||||
description,
|
||||
queryJson: modelHttpErrorsQuery("go"),
|
||||
alertType: "on_change",
|
||||
frequency: 300,
|
||||
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||
recipients: [
|
||||
{
|
||||
id: webhookRecipient.id,
|
||||
notificationDetails: [
|
||||
{
|
||||
variables: [{ name: "type", value: "model_http_errors" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
|
||||
name: "Increased Model HTTP Errors [Zen]",
|
||||
description,
|
||||
queryJson: modelHttpErrorsQuery("zen"),
|
||||
alertType: "on_change",
|
||||
frequency: 300,
|
||||
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||
recipients: [
|
||||
{
|
||||
id: webhookRecipient.id,
|
||||
notificationDetails: [
|
||||
{
|
||||
variables: [{ name: "type", value: "model_http_errors" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedProviderHttpErrorsGo", {
|
||||
name: "Increased Provider HTTP Errors [Go]",
|
||||
description,
|
||||
queryJson: providerHttpErrorsQuery("go"),
|
||||
alertType: "on_change",
|
||||
frequency: 300,
|
||||
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||
recipients: [
|
||||
{
|
||||
id: webhookRecipient.id,
|
||||
notificationDetails: [
|
||||
{
|
||||
variables: [{ name: "type", value: "provider_http_errors" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedProviderHttpErrorsZen", {
|
||||
name: "Increased Provider HTTP Errors [Zen]",
|
||||
description,
|
||||
queryJson: providerHttpErrorsQuery("zen"),
|
||||
alertType: "on_change",
|
||||
frequency: 300,
|
||||
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||
recipients: [
|
||||
{
|
||||
id: webhookRecipient.id,
|
||||
notificationDetails: [
|
||||
{
|
||||
variables: [{ name: "type", value: "provider_http_errors" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedFreeTierRequests", {
|
||||
name: "Increased Free Tier Requests",
|
||||
description,
|
||||
queryJson: honeycomb.getQuerySpecificationOutput({
|
||||
calculations: [{ op: "COUNT" }],
|
||||
filters: [
|
||||
{ column: "event_type", op: "=", value: "completions" },
|
||||
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||
{ column: "isFreeTier", op: "=", value: "true" },
|
||||
],
|
||||
timeRange: 3600,
|
||||
}).json,
|
||||
alertType: "on_change",
|
||||
frequency: 900,
|
||||
thresholds: [{ op: ">=", value: 50, exceededLimit: 1 }],
|
||||
baselineDetails: [{ type: "percentage", offsetMinutes: 1440 }],
|
||||
recipients: [
|
||||
{
|
||||
id: webhookRecipient.id,
|
||||
notificationDetails: [
|
||||
{
|
||||
variables: [{ name: "type", value: "custom" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,4 +1,11 @@
|
||||
sst.Linkable.wrap(random.RandomPassword, (resource) => ({
|
||||
properties: {
|
||||
value: resource.result,
|
||||
},
|
||||
}))
|
||||
|
||||
export const SECRET = {
|
||||
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
|
||||
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
|
||||
HoneycombWebhookSecret: new random.RandomPassword("HoneycombWebhookSecret", { length: 24 }),
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-AgHhYsiygxbsBo3JN4HqHXKAwh8n1qeuSCe2qqxlxW4=",
|
||||
"aarch64-linux": "sha256-h2lpWRQ5EDYnjpqZXtUAp1mxKLQxJ4m8MspgSY8Ev78=",
|
||||
"aarch64-darwin": "sha256-xnd91+WyeAqn06run2ajsekxJvTMiLsnqNPe/rR8VTM=",
|
||||
"x86_64-darwin": "sha256-rXpz45IOjGEk73xhP9VY86eOj2CZBg2l1vzwzTIOOOQ="
|
||||
"x86_64-linux": "sha256-Q9r1S15YL9LQK7DRhuOpw3Fxi24BPovEM995GZJayKw=",
|
||||
"aarch64-linux": "sha256-C0rRTLnxxuuEkCBc3JZbkR66TUVwpcPFif3BU9GRAuA=",
|
||||
"aarch64-darwin": "sha256-1HvalOO/pOkRlYH8CZ93psapt90C+pYzui1JCadBE1Q=",
|
||||
"x86_64-darwin": "sha256-RrndyLWfhWm4mZ88XytFF2NI+ly8la550Z5LBN/g5u4="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ stdenvNoCC.mkDerivation {
|
||||
--filter './packages/opencode' \
|
||||
--filter './packages/desktop' \
|
||||
--filter './packages/app' \
|
||||
--filter './packages/shared' \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--no-progress
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
[
|
||||
ripgrep
|
||||
]
|
||||
# bun runs sysctl to detect if dunning on rosetta2
|
||||
# bun runs sysctl to detect if running on rosetta2
|
||||
++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
|
||||
)
|
||||
}
|
||||
|
||||
+13
-7
@@ -7,12 +7,13 @@
|
||||
"packageManager": "bun@1.3.13",
|
||||
"scripts": {
|
||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||
"dev:desktop": "bun --cwd packages/desktop-electron dev",
|
||||
"dev:desktop": "bun --cwd packages/desktop dev",
|
||||
"dev:web": "bun --cwd packages/app dev",
|
||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||
"lint": "oxlint",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
|
||||
"prepare": "husky",
|
||||
"random": "echo 'Random script'",
|
||||
@@ -27,32 +28,34 @@
|
||||
"packages/slack"
|
||||
],
|
||||
"catalog": {
|
||||
"@effect/opentelemetry": "4.0.0-beta.48",
|
||||
"@effect/platform-node": "4.0.0-beta.48",
|
||||
"@effect/opentelemetry": "4.0.0-beta.65",
|
||||
"@effect/platform-node": "4.0.0-beta.65",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@types/bun": "1.3.12",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"@opentui/core": "0.2.6",
|
||||
"@opentui/keymap": "0.2.6",
|
||||
"@opentui/solid": "0.2.6",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "22.13.9",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/semver": "7.7.1",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"effect": "4.0.0-beta.48",
|
||||
"effect": "4.0.0-beta.65",
|
||||
"ai": "6.0.168",
|
||||
"cross-spawn": "7.0.6",
|
||||
"hono": "4.10.7",
|
||||
@@ -76,6 +79,8 @@
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"solid-js": "1.9.10",
|
||||
"vite-plugin-solid": "2.11.10",
|
||||
"@lydell/node-pty": "1.2.0-beta.10"
|
||||
@@ -128,6 +133,7 @@
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.22",
|
||||
"version": "1.14.48",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -27,6 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "20.0.11",
|
||||
"@playwright/test": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -40,9 +41,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/shared": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/audio": "1.4.2",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@/index.css"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { I18nProvider } from "@opencode-ai/ui/context"
|
||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
||||
@@ -82,7 +83,15 @@ declare global {
|
||||
}
|
||||
|
||||
function QueryProvider(props: ParentProps) {
|
||||
const client = new QueryClient()
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
return <QueryClientProvider client={client}>{props.children}</QueryClientProvider>
|
||||
}
|
||||
|
||||
@@ -140,12 +149,19 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
||||
>
|
||||
<LanguageProvider locale={props.locale}>
|
||||
<UiI18nBridge>
|
||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
<ErrorBoundary
|
||||
fallback={(error) => {
|
||||
Sentry.captureException(error)
|
||||
return <ErrorPage error={error} />
|
||||
}}
|
||||
>
|
||||
<QueryProvider>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
</QueryProvider>
|
||||
</ErrorBoundary>
|
||||
</UiI18nBridge>
|
||||
</LanguageProvider>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createStore } from "solid-js/store"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
||||
import { getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { getProjectAvatarSource } from "@/pages/layout/sidebar-items"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { List } from "@opencode-ai/ui/list"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
|
||||
import { base64Encode } from "@opencode-ai/shared/util/encode"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { useLanguage } from "@/context/language"
|
||||
|
||||
interface ForkableMessage {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import type { ListRef } from "@opencode-ai/ui/list"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import { createMemo, createResource, createSignal } from "solid-js"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
|
||||
@@ -4,8 +4,8 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import { base64Encode } from "@opencode-ai/shared/util/encode"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
|
||||
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMutation } from "@tanstack/solid-query"
|
||||
import { Component, createEffect, createMemo, on, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
import { Component, createMemo, Show } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { mcpQueryKey } from "@/context/global-sync"
|
||||
|
||||
const statusLabels = {
|
||||
connected: "mcp.status.connected",
|
||||
@@ -20,48 +19,7 @@ export const DialogSelectMcp: Component = () => {
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const language = useLanguage()
|
||||
const [state, setState] = createStore({
|
||||
done: false,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => sync.data.mcp_ready,
|
||||
(ready, prev) => {
|
||||
if (!ready && prev) setState("done", false)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (state.done || state.loading) return
|
||||
if (sync.data.mcp_ready) {
|
||||
setState("done", true)
|
||||
return
|
||||
}
|
||||
|
||||
setState("loading", true)
|
||||
void sdk.client.mcp
|
||||
.status()
|
||||
.then((result) => {
|
||||
sync.set("mcp", result.data ?? {})
|
||||
sync.set("mcp_ready", true)
|
||||
setState("done", true)
|
||||
})
|
||||
.catch((err) => {
|
||||
setState("done", true)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
setState("loading", false)
|
||||
})
|
||||
})
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const items = createMemo(() =>
|
||||
Object.entries(sync.data.mcp ?? {})
|
||||
@@ -71,16 +29,10 @@ export const DialogSelectMcp: Component = () => {
|
||||
|
||||
const toggle = useMutation(() => ({
|
||||
mutationFn: async (name: string) => {
|
||||
const status = sync.data.mcp[name]
|
||||
if (status?.status === "connected") {
|
||||
await sdk.client.mcp.disconnect({ name })
|
||||
} else {
|
||||
await sdk.client.mcp.connect({ name })
|
||||
}
|
||||
|
||||
const result = await sdk.client.mcp.status()
|
||||
if (result.data) sync.set("mcp", result.data)
|
||||
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
|
||||
else await sdk.client.mcp.connect({ name })
|
||||
},
|
||||
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
|
||||
}))
|
||||
|
||||
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@/context/prompt"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useComments } from "@/context/comments"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
@@ -102,6 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const sdk = useSDK()
|
||||
const globalSDK = useGlobalSDK()
|
||||
|
||||
const sync = useSync()
|
||||
const local = useLocal()
|
||||
@@ -270,7 +272,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 })
|
||||
const motion = (value: number) => ({
|
||||
opacity: value,
|
||||
transform: `scale(${0.95 + value * 0.05})`,
|
||||
transform: `scale(${0.98 + value * 0.02})`,
|
||||
filter: `blur(${(1 - value) * 2}px)`,
|
||||
"pointer-events": value > 0.5 ? ("auto" as const) : ("none" as const),
|
||||
})
|
||||
@@ -345,7 +347,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
promptPlaceholder({
|
||||
mode: store.mode,
|
||||
commentCount: commentCount(),
|
||||
example: suggest() ? language.t(EXAMPLES[store.placeholder]) : "",
|
||||
example: suggest() ? (store.mode === "shell" ? "git status" : language.t(EXAMPLES[store.placeholder])) : "",
|
||||
suggest: suggest(),
|
||||
t: (key, params) => language.t(key as Parameters<typeof language.t>[0], params as never),
|
||||
}),
|
||||
@@ -1253,7 +1255,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
||||
queries: [loadAgentsQuery(sdk.directory), loadProvidersQuery(null), loadProvidersQuery(sdk.directory)],
|
||||
queries: [
|
||||
loadAgentsQuery(sdk.directory, sdk.client),
|
||||
loadProvidersQuery(null, globalSDK.client),
|
||||
loadProvidersQuery(sdk.directory, sdk.client),
|
||||
],
|
||||
}))
|
||||
|
||||
const agentsLoading = () => agentsQuery.isLoading
|
||||
@@ -1403,12 +1409,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<IconButton
|
||||
data-action="prompt-submit"
|
||||
type="submit"
|
||||
disabled={store.mode !== "normal" || (!working() && blank())}
|
||||
disabled={!working() && blank()}
|
||||
tabIndex={store.mode === "normal" ? undefined : -1}
|
||||
icon={stopping() ? "stop" : "arrow-up"}
|
||||
icon={stopping() ? "stop" : store.mode === "shell" ? "arrow-undo-down" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="size-8"
|
||||
style={buttons()}
|
||||
aria-label={stopping() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -1451,14 +1456,24 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<div class="px-1.75 pt-5.5 pb-2 flex items-center gap-2 min-w-0">
|
||||
<div class="flex items-center gap-1.5 min-w-0 flex-1 relative">
|
||||
<div
|
||||
class="h-7 flex items-center gap-1.5 max-w-[160px] min-w-0 absolute inset-y-0 left-0"
|
||||
class="h-7 flex items-center gap-1.5 min-w-0 absolute inset-0"
|
||||
style={{
|
||||
padding: "0 4px 0 8px",
|
||||
padding: "0 0px 0 8px",
|
||||
...shell(),
|
||||
}}
|
||||
>
|
||||
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
|
||||
<div class="size-4 shrink-0" />
|
||||
<Icon name="console" />
|
||||
<span class="truncate text-13-medium text-text-base">{language.t("prompt.mode.shell")}</span>
|
||||
<div class="flex-1" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="text-text-base"
|
||||
onClick={() => {
|
||||
setStore("mode", "normal")
|
||||
}}
|
||||
>
|
||||
{language.t("common.cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 min-w-0 flex-1 h-7">
|
||||
<Show when={!agentsLoading()}>
|
||||
@@ -1565,33 +1580,35 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</TooltipKeybind>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
data-component="prompt-variant-control"
|
||||
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||
>
|
||||
<TooltipKeybind
|
||||
placement="top"
|
||||
gutter={4}
|
||||
title={language.t("command.model.variant.cycle")}
|
||||
keybind={command.keybind("model.variant.cycle")}
|
||||
<Show when={variants().length > 2}>
|
||||
<div
|
||||
data-component="prompt-variant-control"
|
||||
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||
>
|
||||
<Select
|
||||
size="normal"
|
||||
options={variants()}
|
||||
current={local.model.variant.current() ?? "default"}
|
||||
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||
onSelect={(value) => {
|
||||
local.model.variant.set(value === "default" ? undefined : value)
|
||||
restoreFocus()
|
||||
}}
|
||||
class="capitalize max-w-[160px] text-text-base"
|
||||
valueClass="truncate text-13-regular text-text-base"
|
||||
triggerStyle={control()}
|
||||
triggerProps={{ "data-action": "prompt-model-variant" }}
|
||||
variant="ghost"
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
</div>
|
||||
<TooltipKeybind
|
||||
placement="top"
|
||||
gutter={4}
|
||||
title={language.t("command.model.variant.cycle")}
|
||||
keybind={command.keybind("model.variant.cycle")}
|
||||
>
|
||||
<Select
|
||||
size="normal"
|
||||
options={variants()}
|
||||
current={local.model.variant.current() ?? "default"}
|
||||
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||
onSelect={(value) => {
|
||||
local.model.variant.set(value === "default" ? undefined : value)
|
||||
restoreFocus()
|
||||
}}
|
||||
class="capitalize max-w-[160px] text-text-base"
|
||||
valueClass="truncate text-13-regular text-text-base"
|
||||
triggerStyle={control()}
|
||||
triggerProps={{ "data-action": "prompt-model-variant" }}
|
||||
variant="ghost"
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client"
|
||||
import type { FileSelection } from "@/context/file"
|
||||
import { encodeFilePath } from "@/context/file/path"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, For, Show } from "solid-js"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/shared/util/path"
|
||||
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/core/util/path"
|
||||
import type { ContextItem } from "@/context/prompt"
|
||||
|
||||
type PromptContextItem = ContextItem & { key: string }
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("promptPlaceholder", () => {
|
||||
suggest: true,
|
||||
t,
|
||||
})
|
||||
expect(value).toBe("prompt.placeholder.shell")
|
||||
expect(value).toBe("prompt.placeholder.shell:example")
|
||||
})
|
||||
|
||||
test("returns summarize placeholders for comment context", () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ type PromptPlaceholderInput = {
|
||||
}
|
||||
|
||||
export function promptPlaceholder(input: PromptPlaceholderInput) {
|
||||
if (input.mode === "shell") return input.t("prompt.placeholder.shell")
|
||||
if (input.mode === "shell") return input.t("prompt.placeholder.shell", { example: input.example })
|
||||
if (input.commentCount > 1) return input.t("prompt.placeholder.summarizeComments")
|
||||
if (input.commentCount === 1) return input.t("prompt.placeholder.summarizeComment")
|
||||
if (!input.suggest) return input.t("prompt.placeholder.simple")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, For, Match, Show, Switch } from "solid-js"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||
|
||||
export type AtOption =
|
||||
| { type: "agent"; name: string; display: string }
|
||||
|
||||
@@ -74,7 +74,7 @@ beforeAll(async () => {
|
||||
showToast: () => 0,
|
||||
}))
|
||||
|
||||
mock.module("@opencode-ai/shared/util/encode", () => ({
|
||||
mock.module("@opencode-ai/core/util/encode", () => ({
|
||||
base64Encode: (value: string) => value,
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Message, Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { base64Encode } from "@opencode-ai/shared/util/encode"
|
||||
import { Binary } from "@opencode-ai/shared/util/binary"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { useNavigate, useParams } from "@solidjs/router"
|
||||
import { batch, type Accessor } from "solid-js"
|
||||
import type { FileSelection } from "@/context/file"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
|
||||
import type { JSX } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { checksum } from "@opencode-ai/shared/util/encode"
|
||||
import { findLast } from "@opencode-ai/shared/util/array"
|
||||
import { checksum } from "@opencode-ai/core/util/encode"
|
||||
import { findLast } from "@opencode-ai/core/util/array"
|
||||
import { same } from "@/utils/same"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Accordion } from "@opencode-ai/ui/accordion"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Keybind } from "@opencode-ai/ui/keybind"
|
||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||
import { getFilename } from "@opencode-ai/shared/util/path"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Portal } from "solid-js/web"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user