mirror of
https://github.com/radareorg/r2ai.git
synced 2026-11-09 17:46:36 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9077de9162 | ||
|
|
287fad0ecd | ||
|
|
08afde5117 | ||
|
|
a086a12f1d | ||
|
|
4a86d0fc46 | ||
|
|
0509bf6755 | ||
|
|
1d55b7f72a | ||
|
|
222f0b7410 | ||
|
|
065288e29f | ||
|
|
5824aa4482 | ||
|
|
3c7ed851d8 | ||
|
|
1586d27e93 | ||
|
|
898855d7ce | ||
|
|
20a3751624 | ||
|
|
d81957d856 | ||
|
|
b6d7c9424c | ||
|
|
c3efc53c27 | ||
|
|
b6d234c154 | ||
|
|
ef7f49a68e | ||
|
|
d1fcea6859 | ||
|
|
96dcc0dd4f | ||
|
|
659e14a4cd | ||
|
|
926ef545d0 | ||
|
|
1b11c648de | ||
|
|
394e55cf87 | ||
|
|
8db446cf31 | ||
|
|
65d7d87539 | ||
|
|
372b38380f | ||
|
|
72b9ff2f49 | ||
|
|
742d3f2a51 | ||
|
|
1f3f93fa35 | ||
|
|
3f1b91fbce | ||
|
|
acd0513ebb | ||
|
|
c44e838238 | ||
|
|
f4fa4ba432 | ||
|
|
561db876ac | ||
|
|
bcdc182d03 | ||
|
|
19cba835e4 | ||
|
|
87a1e89112 | ||
|
|
012118c25c | ||
|
|
e2030b31a1 | ||
|
|
53f437d55a | ||
|
|
66f202254e | ||
|
|
8e9555d144 | ||
|
|
a8caa51e76 | ||
|
|
021aa205f0 | ||
|
|
92c86f94e6 | ||
|
|
50c1d63a47 | ||
|
|
79f01945b7 | ||
|
|
9043638e01 | ||
|
|
6bd0227382 | ||
|
|
9f9a3b87ae | ||
|
|
b68ddaeaed | ||
|
|
d2663f75dc | ||
|
|
e24dbab677 | ||
|
|
c765269b02 | ||
|
|
55b856e70b | ||
|
|
5e148bc882 | ||
|
|
9f93754483 | ||
|
|
d262fabb2b | ||
|
|
d0a1f292f5 | ||
|
|
dad02ac243 | ||
|
|
02c561c415 | ||
|
|
9414cdf72b | ||
|
|
7cc61c4cc5 | ||
|
|
e4875eeb37 | ||
|
|
888251a2ea | ||
|
|
af6153ad64 | ||
|
|
87e4e7b232 | ||
|
|
cc3a003a64 | ||
|
|
f2be5cc3c2 | ||
|
|
e360148931 | ||
|
|
d06a9ee361 | ||
|
|
2e7b97b065 | ||
|
|
cd5d9a1640 | ||
|
|
e606392aaf | ||
|
|
8bf1169844 | ||
|
|
90e3bbafc5 | ||
|
|
e5a0639b88 | ||
|
|
80f27cd4d9 | ||
|
|
455da7bdba | ||
|
|
623e98ed3d | ||
|
|
17bf56b5e3 | ||
|
|
f50a7836eb | ||
|
|
2d9b69c624 | ||
|
|
de9c7c904c | ||
|
|
6b3ea982cd | ||
|
|
209d9257ee | ||
|
|
b840d002ee | ||
|
|
9d900b2a6b | ||
|
|
227b15e793 | ||
|
|
4b9dbf7686 | ||
|
|
8ca32e258f | ||
|
|
449c41eae6 | ||
|
|
5210fe6892 | ||
|
|
6222791075 | ||
|
|
05ce908e3a | ||
|
|
b8457c0bfa | ||
|
|
6a256b9210 | ||
|
|
8cc748ab4c | ||
|
|
17df397a3d | ||
|
|
fcd464f1cc | ||
|
|
1f41b6d2d7 | ||
|
|
adf8189541 | ||
|
|
cb8f3548ff | ||
|
|
b99034e5d8 | ||
|
|
fa1f6c1d3c | ||
|
|
d9fb7b18df | ||
|
|
e0b9ff235c | ||
|
|
e1bf58d4e4 | ||
|
|
51a63ac7ef | ||
|
|
c3a3eff046 | ||
|
|
37b5c2b6d0 | ||
|
|
0e6a8cc2e2 | ||
|
|
8dcc6f0418 | ||
|
|
b68cdd1171 | ||
|
|
3f935a46be | ||
|
|
fd46390d41 | ||
|
|
63106508dc | ||
|
|
46f42d3dd9 | ||
|
|
836fae3d91 | ||
|
|
66dd3dced7 | ||
|
|
c016fc72ed | ||
|
|
779b787f2d | ||
|
|
ca210f149b | ||
|
|
99e99b1797 | ||
|
|
fb5427238b | ||
|
|
d7c2dacd7a | ||
|
|
d6c32f4b72 | ||
|
|
0a083e7231 | ||
|
|
1a067fbbfc | ||
|
|
8a724d3682 | ||
|
|
8f7d152aa3 | ||
|
|
7120c325da | ||
|
|
14c991cd10 | ||
|
|
d1ef1e065e | ||
|
|
6f8ec21a39 | ||
|
|
3db0744c91 | ||
|
|
3306e412e2 | ||
|
|
20f5e16164 | ||
|
|
660249a31f | ||
|
|
312921c2a1 | ||
|
|
2356dbef9e | ||
|
|
7612a80a9c | ||
|
|
12d8bc9e22 | ||
|
|
d4be5f8569 | ||
|
|
a9333c8c38 | ||
|
|
29bdb2fcea | ||
|
|
078376c5d2 | ||
|
|
aa24702335 | ||
|
|
9aa40216a2 | ||
|
|
96e454e0c6 | ||
|
|
219b034261 | ||
|
|
797c967fb0 | ||
|
|
e61b7db8a5 | ||
|
|
3a39f9cd9d | ||
|
|
2b5e50d8f0 | ||
|
|
bb737f5be6 | ||
|
|
3e5f91e631 | ||
|
|
a3154bdcaf | ||
|
|
a6be6e6555 | ||
|
|
ad0bdbb902 | ||
|
|
4651d3b1b3 | ||
|
|
572e02c4ec | ||
|
|
75c787ef41 | ||
|
|
731f1933a9 | ||
|
|
573755ccef | ||
|
|
ecca7825fe | ||
|
|
95117608fb | ||
|
|
b3958b5f7d | ||
|
|
d0eaf37764 | ||
|
|
65692edd54 | ||
|
|
50f55bb5d2 | ||
|
|
fe1768fcc3 | ||
|
|
f241fba17d | ||
|
|
e5252960e0 | ||
|
|
8a45dadbde | ||
|
|
53d32efbbe | ||
|
|
f06faba9d6 | ||
|
|
8b52c2f6e2 | ||
|
|
b37436fb8d | ||
|
|
2524e66825 | ||
|
|
519017a75e | ||
|
|
6f30d6ca37 | ||
|
|
08ea4d11b2 | ||
|
|
0281308434 | ||
|
|
17a59d2057 | ||
|
|
eac0b8c6ef | ||
|
|
24aee426b4 | ||
|
|
b31bc5c116 | ||
|
|
01738210f5 | ||
|
|
7b19c4ff99 | ||
|
|
1986dd88bd | ||
|
|
1ed40401dd | ||
|
|
6ac0ff6352 | ||
|
|
4158e5f157 | ||
|
|
225ff58228 | ||
|
|
a51be44c68 | ||
|
|
911e455f72 | ||
|
|
70eb975b7f | ||
|
|
a0d36cc909 | ||
|
|
030551816b | ||
|
|
88c44c871b | ||
|
|
9218e3ea27 | ||
|
|
6f7ffcae1a | ||
|
|
fe12d2f8ee | ||
|
|
ae55b2f204 | ||
|
|
7d4e949240 | ||
|
|
9b97d39c7a | ||
|
|
33f72a997e | ||
|
|
36a8f13131 | ||
|
|
5c2ded0171 | ||
|
|
d5a39816a8 | ||
|
|
5aab38ab76 | ||
|
|
9f15ec6c73 | ||
|
|
68baac9911 | ||
|
|
54a280952e | ||
|
|
efc2368af1 | ||
|
|
28ddcbf638 | ||
|
|
85e9ed5c65 | ||
|
|
8886365925 | ||
|
|
01d8e29420 | ||
|
|
e8b556ee5d | ||
|
|
6c336f48f7 | ||
|
|
53d61831b2 | ||
|
|
cbcff6b9dd | ||
|
|
f04bf8162e | ||
|
|
0206ceb390 | ||
|
|
ff1f788c48 | ||
|
|
df135b2c70 | ||
|
|
c152777bbe | ||
|
|
3e084ce797 | ||
|
|
7fce8788b5 | ||
|
|
585e30470e | ||
|
|
5594e64ef3 | ||
|
|
904a356290 | ||
|
|
d1e2bcf911 | ||
|
|
21528f7ea8 | ||
|
|
2883d134aa | ||
|
|
ec96ba3329 | ||
|
|
f92ea17b01 | ||
|
|
2dc92031e7 | ||
|
|
55d54b4953 | ||
|
|
35460e639c | ||
|
|
a5382c17c4 | ||
|
|
f4bdb847db | ||
|
|
a7c3d3767c | ||
|
|
97c47ab28a | ||
|
|
c36bc1500c | ||
|
|
179cb6ca58 | ||
|
|
402c31cc96 | ||
|
|
be14729424 | ||
|
|
aa9b8e0fba | ||
|
|
96225cd555 | ||
|
|
321ca1bb41 | ||
|
|
d94ed5a088 | ||
|
|
3537d006de | ||
|
|
bdcee9e736 | ||
|
|
074b7a6c9f | ||
|
|
6697df8437 | ||
|
|
ff0fb4c802 | ||
|
|
ca7d02e03c | ||
|
|
bd105b2b0e | ||
|
|
5e18525403 | ||
|
|
c9d0756e74 | ||
|
|
d3fc79f9d1 | ||
|
|
8f4c972beb | ||
|
|
f95cda42f7 | ||
|
|
b241f30432 | ||
|
|
8eb50e466b | ||
|
|
14b5dcbfb9 | ||
|
|
e264f8f0c8 | ||
|
|
46c2ce6c62 | ||
|
|
693ad9270c | ||
|
|
8446af7385 | ||
|
|
5a156a0126 | ||
|
|
a28efa7a33 | ||
|
|
bc5adb3fcb | ||
|
|
4bd1d911e0 | ||
|
|
41c9734c69 | ||
|
|
1a41298dd6 | ||
|
|
c618f8ed09 | ||
|
|
48932c5c64 | ||
|
|
4da20a5c33 | ||
|
|
b7f94ad419 | ||
|
|
2e45fb9e9c | ||
|
|
9775e94455 | ||
|
|
67416766e2 | ||
|
|
5c1be40048 | ||
|
|
49e87c7b7a | ||
|
|
11e7c288e0 | ||
|
|
909f4738f8 | ||
|
|
0148a64aa7 | ||
|
|
d8df27a4c7 | ||
|
|
6608dbcf3c | ||
|
|
ef83ddefc9 | ||
|
|
c19fd205e3 | ||
|
|
eec673f8db | ||
|
|
5e20b37e08 | ||
|
|
547418adf0 | ||
|
|
e161577bcb | ||
|
|
ba186d7c57 | ||
|
|
24c1e3fcb4 | ||
|
|
1f001080ff | ||
|
|
a189a682b6 | ||
|
|
a0299f791d | ||
|
|
79214fb22c | ||
|
|
775d3243e6 | ||
|
|
cd514ac099 | ||
|
|
6f352e1966 | ||
|
|
32458a653e | ||
|
|
b32fd6097f | ||
|
|
df2fc67e2c | ||
|
|
b318c30865 | ||
|
|
2610309b58 | ||
|
|
0b81158d01 | ||
|
|
4bcd2c582d | ||
|
|
51aac5f348 | ||
|
|
a437938526 | ||
|
|
c6bba6c7dd | ||
|
|
3103df8210 | ||
|
|
b1fd4aab8e | ||
|
|
26ca088c32 | ||
|
|
710eb93bbe | ||
|
|
fb1f53a176 | ||
|
|
138fd5e7d3 | ||
|
|
bd040f9a48 | ||
|
|
1f3f04d74b | ||
|
|
a7cff9d970 | ||
|
|
a1de8828ac | ||
|
|
7a1d4668d6 | ||
|
|
e20f390878 | ||
|
|
be18fe91ad | ||
|
|
6df2a231eb | ||
|
|
668eee7807 | ||
|
|
111974071f | ||
|
|
b193caec61 | ||
|
|
8994415242 | ||
|
|
7e6953dce6 | ||
|
|
0bcd68c434 | ||
|
|
74e0b23ab0 | ||
|
|
1044bea8dd | ||
|
|
081e34a835 | ||
|
|
9f22b4c3c3 | ||
|
|
4fb8ab0f2e | ||
|
|
8e5b7e0152 | ||
|
|
e40c655013 | ||
|
|
0ebda776cf | ||
|
|
879592f22e | ||
|
|
fd3efa6c70 | ||
|
|
b420645c20 | ||
|
|
211e9af847 | ||
|
|
58e1eb6d14 | ||
|
|
c72fe14f19 | ||
|
|
10162f7302 | ||
|
|
949e22fdf6 | ||
|
|
2355c15cd5 | ||
|
|
c5db441a07 | ||
|
|
9853a99bcb | ||
|
|
4d685520b1 | ||
|
|
a20035fd31 | ||
|
|
733ebf9283 | ||
|
|
595c0ef2d6 | ||
|
|
c86b58a956 | ||
|
|
bd1f39b379 | ||
|
|
71bee067b3 | ||
|
|
713183d2c7 | ||
|
|
1f5406ae55 | ||
|
|
a9e5aa114b | ||
|
|
67c1a6d129 | ||
|
|
91bbd87c2d | ||
|
|
e1f8ddbff3 | ||
|
|
562aeb1ea5 | ||
|
|
2444f12acc | ||
|
|
d0d58670ec | ||
|
|
5eb8231ee1 | ||
|
|
10775172d8 | ||
|
|
5c39cc4c3f | ||
|
|
79ac83ff47 | ||
|
|
6869d9e259 | ||
|
|
dba793411f | ||
|
|
4f095b5e90 | ||
|
|
b4b73d3ceb | ||
|
|
c818e170a0 | ||
|
|
88955740d7 | ||
|
|
1859d64ca7 | ||
|
|
5af7f44498 | ||
|
|
0fb9ce8e8c | ||
|
|
2d444e7159 | ||
|
|
6b2e327fc3 | ||
|
|
6276fe6685 | ||
|
|
3c67d6693f | ||
|
|
35a2139380 | ||
|
|
594e2f4f20 | ||
|
|
5f281591b4 | ||
|
|
829ffa0133 | ||
|
|
2c1b41ad57 | ||
|
|
91ede7cc72 | ||
|
|
4b03d2458c | ||
|
|
57ab90b26a | ||
|
|
eba9d2ca4b | ||
|
|
165cd2bc46 | ||
|
|
c4f1ae900f | ||
|
|
e090521bd2 | ||
|
|
094fc325c5 | ||
|
|
b557e60b8f | ||
|
|
97e62cde53 | ||
|
|
5b8ee5201e | ||
|
|
fb70b8ad32 | ||
|
|
67d1528895 | ||
|
|
c2aba8ce86 | ||
|
|
c5c0e13b64 | ||
|
|
6fa0ed3d8e | ||
|
|
8fdb499906 | ||
|
|
24b958d040 | ||
|
|
1c9a9973da | ||
|
|
e5dea8baa4 | ||
|
|
739da0859c | ||
|
|
3fdfa351bf | ||
|
|
6b04f5f691 | ||
|
|
5b246273f2 | ||
|
|
2c5634073f | ||
|
|
d8f483e361 | ||
|
|
e7b154ab13 | ||
|
|
e1f90c3c92 | ||
|
|
ee75d5018a | ||
|
|
808b3581db | ||
|
|
c39c0a0757 | ||
|
|
042acc1be0 | ||
|
|
9a12668822 | ||
|
|
b943ec8be1 | ||
|
|
65371a6967 | ||
|
|
3fd244f7f0 | ||
|
|
46d55b3167 | ||
|
|
1bf249188d | ||
|
|
38aeec0454 | ||
|
|
7ad7bf27ed | ||
|
|
647bd799f4 | ||
|
|
9b5b8d2ae6 | ||
|
|
b9a1066556 | ||
|
|
3d7af1d8d9 | ||
|
|
f591d8e03f | ||
|
|
1268ab1567 | ||
|
|
fa2af78c15 | ||
|
|
32a16dff4d | ||
|
|
5e8798be91 | ||
|
|
1ae7a2977d | ||
|
|
c4deedbd17 | ||
|
|
6cd8b99594 | ||
|
|
7ceb4147f8 | ||
|
|
155456912a | ||
|
|
ca8c0f3c07 | ||
|
|
43953507cc | ||
|
|
edce63ea67 | ||
|
|
aac6092151 | ||
|
|
39ddddbc7e | ||
|
|
3b9a325e3b | ||
|
|
ef975edb9d | ||
|
|
060a897a09 | ||
|
|
b687f76566 | ||
|
|
2c747e42d1 | ||
|
|
e48a0d1166 | ||
|
|
5d16354f56 | ||
|
|
5f577c5f7d | ||
|
|
07ee07835d | ||
|
|
c6e3b5149a | ||
|
|
29aa5d8e60 | ||
|
|
6e9cb22f2f | ||
|
|
f7497c653b | ||
|
|
cd3474dd06 | ||
|
|
10ecbba3af | ||
|
|
406d5b2204 | ||
|
|
ea379de711 | ||
|
|
2ee71ec281 | ||
|
|
f3e981f895 | ||
|
|
9adc3f2c6f | ||
|
|
baefc5ae51 | ||
|
|
14181ec917 | ||
|
|
825c3006ec | ||
|
|
c9476904bb | ||
|
|
e32487e244 | ||
|
|
4055d13838 | ||
|
|
95d6554bf7 | ||
|
|
7a8511d29b | ||
|
|
a1ff327d22 | ||
|
|
4329a12a8e | ||
|
|
f663710c0d | ||
|
|
9cb9c8b16c | ||
|
|
3ea6d0d804 | ||
|
|
641d090c08 | ||
|
|
f3c1e2df45 | ||
|
|
8c7fd96436 | ||
|
|
bcc70c0103 | ||
|
|
782ed359ef | ||
|
|
b00b7b52af | ||
|
|
7a40faaf8b | ||
|
|
e572cdb3d1 | ||
|
|
d182f3e3e9 | ||
|
|
6c45fbc8da | ||
|
|
286de473f1 | ||
|
|
005e6d1d88 | ||
|
|
197bb7bab5 | ||
|
|
d9b697d04c | ||
|
|
2e753cafa6 | ||
|
|
b5383f5b87 | ||
|
|
78ef1750de | ||
|
|
565120628f | ||
|
|
1592d4281f | ||
|
|
ec6d35c390 | ||
|
|
e55027f49e | ||
|
|
93544c9eaa | ||
|
|
68c2868674 | ||
|
|
fb5fc88d97 | ||
|
|
235761b01a | ||
|
|
d00d56b47d | ||
|
|
83b22d07f6 | ||
|
|
223a266035 | ||
|
|
18f7908af4 | ||
|
|
dece4a9bdb | ||
|
|
410f084449 | ||
|
|
bb496c1d9e | ||
|
|
f9d8965609 | ||
|
|
e3493c66d6 | ||
|
|
2b9a54fdb2 | ||
|
|
f24e945a84 | ||
|
|
c500ab83c4 | ||
|
|
e3ca8c966d | ||
|
|
52df442873 | ||
|
|
eacd566522 | ||
|
|
c946298245 | ||
|
|
9334046f33 | ||
|
|
4dab688f11 | ||
|
|
ee1881fcd9 | ||
|
|
b19934c1f2 | ||
|
|
8ba25bae69 | ||
|
|
81188c663b | ||
|
|
ad2ac6bf5e | ||
|
|
56af96ee79 | ||
|
|
01cfec0a43 | ||
|
|
4506610075 | ||
|
|
cf8331056f | ||
|
|
43042dca9c | ||
|
|
6485e67501 | ||
|
|
cf5600ed88 | ||
|
|
31dc32fe12 | ||
|
|
e838b7028c | ||
|
|
521025ce76 | ||
|
|
ea71d598ca | ||
|
|
5fde78def8 | ||
|
|
8e1913a873 | ||
|
|
799caf392f | ||
|
|
0c19f7334c | ||
|
|
e4de4b30b2 | ||
|
|
6aa5062d4d | ||
|
|
f330f46888 | ||
|
|
fe2ea28c7e | ||
|
|
a8017b68fa | ||
|
|
936e0f96ab | ||
|
|
37d3d6f383 | ||
|
|
5f07eb0566 | ||
|
|
f877ebb373 | ||
|
|
c00c955431 | ||
|
|
00a6e7185e | ||
|
|
faee05108a | ||
|
|
0a441a1d41 | ||
|
|
807b37d6cf | ||
|
|
5ae84a589d | ||
|
|
c1876aa8a9 |
No files matched your search
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- buildsystem
|
||||
+162
-39
@@ -1,47 +1,170 @@
|
||||
# TODO: add windows
|
||||
name: ci
|
||||
name: CI and Release
|
||||
|
||||
env:
|
||||
R2V: 5.9.0
|
||||
R2V: 6.2.6
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
branches:
|
||||
- '**'
|
||||
tags:
|
||||
- '[0-9]*'
|
||||
# pull_request:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
build:
|
||||
name: Build (${{ matrix.os }} / ${{ matrix.build_system }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-24.04, macos-latest, windows-latest]
|
||||
build_system: [make, meson]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
build_system: make
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Installing radare2
|
||||
run: |
|
||||
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
|
||||
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
|
||||
sudo dpkg -i "radare2_${R2V}_amd64.deb"
|
||||
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
|
||||
- name: Setting up the Python virtual environment
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install pylint
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pip install .
|
||||
- name: Linting
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
make cilint
|
||||
- name: Running r2ai from the shell
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python -m r2ai.cli -h
|
||||
- name: Running r2ai from r2pipe
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
r2 -q -c '#!pipe python -m r2ai.cli -h' /bin/ls
|
||||
- name: Running r2ai from r2
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
r2 -q -i r2ai/plugin.py -c r2ai /bin/ls
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Meson/Ninja
|
||||
if: matrix.build_system == 'meson'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install meson ninja
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Build with Make
|
||||
if: matrix.build_system == 'make'
|
||||
run: make -C src
|
||||
|
||||
- name: Build with Meson (Unix)
|
||||
if: matrix.build_system == 'meson' && matrix.os != 'windows-latest'
|
||||
run: |
|
||||
cd src
|
||||
meson setup build || meson configure build
|
||||
meson compile -C build
|
||||
|
||||
- name: Build with Meson (Windows)
|
||||
if: matrix.build_system == 'meson' && matrix.os == 'windows-latest'
|
||||
shell: powershell
|
||||
run: |
|
||||
cd src
|
||||
$prefix = "${{ steps.r2.outputs.prefix }}"
|
||||
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
|
||||
if ($LASTEXITCODE -ne 0) { meson configure build }
|
||||
meson compile -C build
|
||||
|
||||
bindist:
|
||||
name: Build r2pm binary (${{ matrix.os }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install radare2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Package r2pm binary
|
||||
run: make -C src bindist
|
||||
|
||||
- name: Upload r2pm binary artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: bindist-${{ matrix.os }}
|
||||
path: r2ai-*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
windows-bindist:
|
||||
name: Build r2pm binary (Windows)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Meson/Ninja
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install meson ninja
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Build with Meson
|
||||
shell: powershell
|
||||
run: |
|
||||
cd src
|
||||
$prefix = "${{ steps.r2.outputs.prefix }}"
|
||||
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
|
||||
if ($LASTEXITCODE -ne 0) { meson configure build }
|
||||
meson compile -C build
|
||||
|
||||
- name: Package r2pm binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path bindist\plugins,bindist\bin,bindist\prompts | Out-Null
|
||||
Get-ChildItem src\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
|
||||
Get-ChildItem src\..\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
|
||||
Copy-Item src\build\r2ai.exe bindist\bin
|
||||
Copy-Item prompts\*.r2ai.md bindist\prompts
|
||||
$rv = (& radare2 -qv).Trim()
|
||||
Compress-Archive -Path bindist\* -DestinationPath "r2ai-$rv-windows-x86-64.zip" -Force
|
||||
|
||||
- name: Upload r2pm binary artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-bindist
|
||||
path: r2ai-*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [bindist, windows-bindist]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Verify tag is on master
|
||||
run: |
|
||||
git fetch origin master:refs/remotes/origin/master
|
||||
if ! git branch -r --contains ${GITHUB_SHA} | grep -q "origin/master"; then
|
||||
echo "Tag commit is not on origin/master — skipping release."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Download r2pm binary artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: '*bindist*'
|
||||
path: release-artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Add decai.r2.js to release artifacts
|
||||
run: cp decai/decai.r2.js release-artifacts/
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
release-artifacts/*.zip
|
||||
release-artifacts/decai.r2.js
|
||||
generate_release_notes: true
|
||||
+15
-1
@@ -1,3 +1,17 @@
|
||||
build
|
||||
b
|
||||
src/b
|
||||
src/r2ai
|
||||
src/r2ai.dSYM
|
||||
venv
|
||||
**/__pycache__
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
third_party
|
||||
|
||||
# r2pm binary packages
|
||||
r2ai-*.zip
|
||||
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/bin/sh
|
||||
OPENAPI_HOST=localhost
|
||||
OPENAPI_PORT=8080
|
||||
|
||||
if [ -z "${OLLAMA_HOST}" ]; then
|
||||
OLLAMA_HOST=localhost
|
||||
fi
|
||||
if [ -z "${OLLAMA_PORT}" ]; then
|
||||
OLLAMA_PORT=11434
|
||||
fi
|
||||
if [ -z "${OLLAMA_MODEL}" ]; then
|
||||
OLLAMA_MODEL="llama3.2:1b"
|
||||
fi
|
||||
|
||||
GEMINI_KEY=""
|
||||
GEMINI_MODEL="gemini-1.5-flash"
|
||||
if [ -f ~/.r2ai.gemini-key ]; then
|
||||
GEMINI_KEY=$(cat ~/.r2ai.gemini-key)
|
||||
fi
|
||||
OPENAI_KEY=""
|
||||
OPENAI_MODEL="gpt-4o"
|
||||
if [ -f ~/.r2ai.openai-key ]; then
|
||||
OPENAI_KEY=$(cat ~/.r2ai.openai-key)
|
||||
fi
|
||||
CLAUDE_KEY=""
|
||||
CLAUDE_MODEL="claude-sonnet-5-5"
|
||||
if [ -f ~/.r2ai.anthropic-key ]; then
|
||||
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
|
||||
fi
|
||||
DEEPSEEK_KEY=""
|
||||
DEEPSEEK_MODEL="claude-sonnet-5-5"
|
||||
if [ -f ~/.r2ai.deepseek-key ]; then
|
||||
DEEPSEEK_KEY=$(cat ~/.r2ai.anthropic-key)
|
||||
fi
|
||||
|
||||
SCISSORS="------------8<------------"
|
||||
|
||||
read_INPUT() {
|
||||
export INPUT=`(echo "$ARG" ; echo "<INPUT>"; cat ; echo "</INPUT>" ) | jq -R -s .`
|
||||
echo "$INPUT"
|
||||
}
|
||||
|
||||
claude() {
|
||||
read_INPUT
|
||||
PAYLOAD="
|
||||
{
|
||||
\"model\": \"${CLAUDE_MODEL}\",
|
||||
\"max_tokens\": 5128,
|
||||
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
|
||||
}
|
||||
"
|
||||
echo "$SCISSORS"
|
||||
curl -s https://api.anthropic.com/v1/messages \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "x-api-key: ${CLAUDE_KEY}" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.content[0].text'
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
ollama() {
|
||||
read_INPUT
|
||||
PAYLOAD="{ \"stream\":false, \"model\":\"${OLLAMA_MODEL}\", \"messages\": [{\"role\":\"user\", \"content\": ${INPUT} }]}"
|
||||
echo "$SCISSORS"
|
||||
curl -s "http://${OLLAMA_HOST}:${OLLAMA_PORT}/api/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .message.content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
openapi() {
|
||||
read_INPUT
|
||||
PAYLOAD="{ \"prompt\": ${INPUT} }"
|
||||
echo "$SCISSORS"
|
||||
curl -s "http://${OPENAPI_HOST}:${OPENAPI_PORT}/completion" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
openai() {
|
||||
read_INPUT
|
||||
PAYLOAD="
|
||||
{
|
||||
\"model\": \"${OPENAI_MODEL}\",
|
||||
\"max_completion_tokens\": 5128,
|
||||
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
|
||||
}
|
||||
"
|
||||
echo "$SCISSORS"
|
||||
curl -s https://api.openai.com/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${OPENAI_KEY}" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.choices[0].message.content'
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
gemini() {
|
||||
read_INPUT
|
||||
PAYLOAD=" {
|
||||
\"contents\":[{
|
||||
\"parts\":[
|
||||
{\"text\": ${INPUT}}
|
||||
] }] }"
|
||||
echo "$SCISSORS"
|
||||
curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GEMINI_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .candidates[0].content.parts[0].text
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
deepseek() {
|
||||
read_INPUT
|
||||
PAYLOAD=" {
|
||||
\"model\":\"deepseek-chat\",
|
||||
\"stream\":\"false\",
|
||||
\"messages\":[{
|
||||
{\"role\": \"user\", \"content\": ${INPUT}}
|
||||
}]}"
|
||||
echo "$SCISSORS"
|
||||
curl -s -X POST "https://api.deepseek.com/chat/completions" \
|
||||
-H "Authorization: Bearer ${DEEPSEEK_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .choices[0].message.content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
$ ai [--] | [-h] | [prompt] < INPUT
|
||||
-h = show this help message
|
||||
-- = don't display the ---8<--- lines in the output
|
||||
SHAI_API = ollama | gemini | claude | openai
|
||||
OLLAMA_MODEL=hf.co/mradermacher/salamandra-7b-instruct-aina-hack-GGUF:salamandra-7b-instruct-aina-hack.Q4_K_M.gguf
|
||||
OLLAMA_HOST=localhost
|
||||
OLLAMA_PORT=11434
|
||||
GEMINI_KEY=~/.r2ai-gemini.key
|
||||
OPENAI_KEY=~/.r2ai-openai.key
|
||||
CLAUDE_KEY=~/.r2ai-anthropic.key
|
||||
DEEPSEEK_KEY=~/.r2ai-deepseek.key
|
||||
CLAUDE_MODEL=claude-sonnet-5-5
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
[ "$1" = "-h" ] && show_help
|
||||
|
||||
if [ "$1" = "--" ]; then
|
||||
SCISSORS=""
|
||||
shift
|
||||
fi
|
||||
|
||||
export ARG="$@"
|
||||
case "${SHAI_API}" in
|
||||
gemini|google) gemini ; ;;
|
||||
deepseek) deepseek ; ;;
|
||||
openapi) openapi ; ;;
|
||||
claude) claude ; ;;
|
||||
ollama) ollama ; ;;
|
||||
openai) openai ; ;;
|
||||
*) claude ; ;;
|
||||
esac
|
||||
File renamed without changes.
@@ -0,0 +1,14 @@
|
||||
SHELL=/bin/bash
|
||||
|
||||
MODELS=gpt-oss:20b gemma3:4b gemma3:12b
|
||||
MODELS+=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
|
||||
MODELS=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
|
||||
LANGS=Dart Perl Ruby Javascript PHP C
|
||||
|
||||
all:
|
||||
for a in ${MODELS} ; do \
|
||||
declare -a ARGS; \
|
||||
for l in ${LANGS} ; do \
|
||||
ARGS+=("echo \"decai -e model=$$a;decai -e lang=$$l;s 0x100000924;af;decai -d\" | r2 -q /bin/ls"); \
|
||||
done ; \
|
||||
./run.sh "$${ARGS[@]}" > ${a//\//_}.html ; done
|
||||
@@ -0,0 +1,4 @@
|
||||
<b>
|
||||
decai -e model=$a;decai -e lang=$l;s 0x100000924;af;decai -d
|
||||
</b><br/>
|
||||
Transform this pseudocode and respond ONLY with plain code (NO explanations, comments or markdown), Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:', Reduce lines of code and fit everything in a single function, Remove all dead code
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Find all directories containing BENCH.txt
|
||||
dirs=$(find . -name "BENCH.txt" -type f | xargs dirname | sort | uniq)
|
||||
|
||||
# Start HTML output
|
||||
cat <<EOF > benchmark_index.html
|
||||
<html>
|
||||
<table>
|
||||
EOF
|
||||
|
||||
# Process each directory
|
||||
for dir in $dirs; do
|
||||
cat <<EOF >> benchmark_index.html
|
||||
<tr> <td>
|
||||
<div>
|
||||
EOF
|
||||
|
||||
# Add contents of BENCH.txt
|
||||
cat "$dir/BENCH.txt" >> benchmark_index.html
|
||||
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
EOF
|
||||
|
||||
# Find and add links to HTML files
|
||||
for html_file in $(ls "$dir"/*.html 2>/dev/null | sort); do
|
||||
base=$(basename "$html_file" .html)
|
||||
cat <<EOF >> benchmark_index.html
|
||||
<a href="$dir/$base.html">$base</a>
|
||||
<br />
|
||||
EOF
|
||||
done
|
||||
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</td>
|
||||
</tr>
|
||||
EOF
|
||||
done
|
||||
|
||||
# End HTML output
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</table>
|
||||
<br />
|
||||
<br />
|
||||
</html>
|
||||
EOF
|
||||
Executable
+177
@@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate HTML output for command executions with collapsible sections and width control
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 command1 [command2 ...]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Timeout in seconds for each command (configurable via TIMEOUT environment variable)
|
||||
TIMEOUT=${TIMEOUT:-30}
|
||||
|
||||
cat << 'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Command Execution Results</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:root {
|
||||
--max-width: 400px;
|
||||
}
|
||||
body {
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.slider-container {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
}
|
||||
.slider-container label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.slider-container input {
|
||||
width: 100%;
|
||||
}
|
||||
.commands-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.command-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
width: var(--max-width);
|
||||
}
|
||||
.command-title {
|
||||
background: #e9ecef;
|
||||
padding: 12px 15px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
}
|
||||
.command-title:hover {
|
||||
background: #dee2e6;
|
||||
}
|
||||
.command-time {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 15px;
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.command-body {
|
||||
padding: 15px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: none;
|
||||
}
|
||||
.command-output {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.command-section.expanded .command-body {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="slider-container">
|
||||
<label for="width-slider">Maximum Width: <span id="width-value">400</span>px</label>
|
||||
<input type="range" id="width-slider" min="200" max="2000" value="400" step="50">
|
||||
</div>
|
||||
<div class="commands-container">
|
||||
EOF
|
||||
|
||||
# Process each command
|
||||
for cmd in "$@"; do
|
||||
# Extract title: if command starts with #, use the rest as title, else use the command
|
||||
if [[ "$cmd" =~ ^#[[:space:]]*(.*) ]]; then
|
||||
title="${BASH_REMATCH[1]}"
|
||||
exec_cmd="${cmd#*#}"
|
||||
exec_cmd="${exec_cmd#"${exec_cmd%%[![:space:]]*}"}" # trim leading spaces
|
||||
else
|
||||
title="$cmd"
|
||||
exec_cmd="$cmd"
|
||||
fi
|
||||
|
||||
# Execute command and time it
|
||||
start_time=$(date +%s.%N)
|
||||
tmpfile=$(mktemp)
|
||||
eval "$exec_cmd" > "$tmpfile" 2>&1 &
|
||||
pid=$!
|
||||
(sleep $TIMEOUT && kill $pid 2>/dev/null) &
|
||||
wait $pid 2>/dev/null
|
||||
output=$(cat "$tmpfile")
|
||||
rm "$tmpfile"
|
||||
exit_code=$?
|
||||
end_time=$(date +%s.%N)
|
||||
|
||||
# Calculate execution time
|
||||
execution_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
|
||||
if [ $? -ne 0 ]; then
|
||||
execution_time="N/A"
|
||||
else
|
||||
execution_time=$(printf "%.3f" "$execution_time")
|
||||
fi
|
||||
|
||||
# Escape HTML in output
|
||||
escaped_output=$(echo "$output" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||||
|
||||
cat << EOF
|
||||
<div class="command-section expanded">
|
||||
<button class="command-title" onclick="toggleSection(this)">$title<span class="command-time">${execution_time}s</span></button>
|
||||
<div class="command-body">
|
||||
<div class="command-output">$escaped_output</div>
|
||||
</div>
|
||||
</div>
|
||||
EOF
|
||||
done
|
||||
|
||||
cat << 'EOF'
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Slider functionality
|
||||
const slider = document.getElementById('width-slider');
|
||||
const widthValue = document.getElementById('width-value');
|
||||
|
||||
slider.addEventListener('input', function() {
|
||||
const value = this.value;
|
||||
document.documentElement.style.setProperty('--max-width', value + 'px');
|
||||
widthValue.textContent = value;
|
||||
});
|
||||
|
||||
// Toggle functionality
|
||||
function toggleSection(button) {
|
||||
const section = button.parentElement;
|
||||
section.classList.toggle('expanded');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,6 @@
|
||||
import google.generativeai as genai
|
||||
|
||||
genai.configure(api_key="YOUR_API_KEY")
|
||||
model = genai.GenerativeModel("gemini-1.5-flash")
|
||||
response = model.generate_content("Explain how AI works")
|
||||
print(response.text)
|
||||
File renamed without changes.
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
r2pm -r llama-server \
|
||||
--port 8012 -ngl 99 -fa -ub 1024 -b 1024 -dt 0.1 \
|
||||
--ctx-size 0 --cache-reuse 256 -m ~/.r2ai.models/qwen2.5-coder-7b-instruct-q3_k_m.gguf
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ALPHABET_SIZE 26
|
||||
|
||||
// Function to perform substitution (both encryption and decryption)
|
||||
void substitute(const char *input, const char *substitution, char *output, int reverse) {
|
||||
char mapping[ALPHABET_SIZE];
|
||||
|
||||
if (reverse) {
|
||||
// Create reverse substitution table for decryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[substitution[i] - 'A'] = 'A' + i;
|
||||
}
|
||||
} else {
|
||||
// Use the direct substitution table for encryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[i] = substitution[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; input[i] != '\0'; i++) {
|
||||
char c = input[i];
|
||||
|
||||
// Handle uppercase letters
|
||||
if (isupper(c)) {
|
||||
output[i] = mapping[c - 'A'];
|
||||
}
|
||||
// Handle lowercase letters
|
||||
else if (islower(c)) {
|
||||
output[i] = tolower(mapping[c - 'a']);
|
||||
}
|
||||
// Leave non-alphabet characters unchanged
|
||||
else {
|
||||
output[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
output[strlen(input)] = '\0'; // Null-terminate the output string
|
||||
}
|
||||
|
||||
int main() {
|
||||
char input[256];
|
||||
char output[256];
|
||||
|
||||
// Configurable substitution key (A-Z)
|
||||
const char *substitution = "QWERTYUIOPASDFGHJKLZXCVBNM"; // Example substitution key
|
||||
|
||||
// Get input from the user
|
||||
printf("Enter text to encrypt: ");
|
||||
fgets(input, sizeof(input), stdin);
|
||||
|
||||
// Remove newline character from input if it exists
|
||||
input[strcspn(input, "\n")] = 0;
|
||||
|
||||
// Perform encryption (forward substitution)
|
||||
substitute(input, substitution, output, 0);
|
||||
printf("Encrypted text: %s\n", output);
|
||||
|
||||
// Perform decryption (reverse substitution)
|
||||
substitute(output, substitution, input, 1);
|
||||
printf("Decrypted text: %s\n", input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,28 @@
|
||||
from transformers import AutoTokenizer
|
||||
import transformers
|
||||
import torch
|
||||
|
||||
model = "facebook/llm-compiler-13b"
|
||||
model = "QuantFactory/llm-compiler-7b-GGUF"
|
||||
# model = "file:///tmp/llm-compiler.gguf"
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(model)
|
||||
pipeline = transformers.pipeline(
|
||||
"text-generation",
|
||||
model=model,
|
||||
torch_dtype=torch.float16,
|
||||
device_map="auto",
|
||||
)
|
||||
|
||||
sequences = pipeline(
|
||||
'%3 = alloca i32, align 4',
|
||||
do_sample=True,
|
||||
top_k=10,
|
||||
temperature=0.1,
|
||||
top_p=0.95,
|
||||
num_return_sequences=1,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
max_length=200,
|
||||
)
|
||||
for seq in sequences:
|
||||
print(f"Result: {seq['generated_text']}")
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,249 @@
|
||||
import Foundation
|
||||
|
||||
// Token representation
|
||||
struct Token {
|
||||
var token: String
|
||||
var count: Int
|
||||
var df: Int
|
||||
}
|
||||
|
||||
// KD-Tree Node
|
||||
class KDNodeData {
|
||||
let point: [Float]
|
||||
let text: String
|
||||
var left: KDNodeData?
|
||||
var right: KDNodeData?
|
||||
let splitDim: Int
|
||||
|
||||
init(point: [Float], text: String, left: KDNodeData? = nil, right: KDNodeData? = nil, splitDim: Int) {
|
||||
self.point = point
|
||||
self.text = text
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.splitDim = splitDim
|
||||
}
|
||||
}
|
||||
|
||||
// Result for K-Nearest Neighbors
|
||||
struct KNNResult {
|
||||
let node: KDNodeData
|
||||
let distanceSq: Float
|
||||
}
|
||||
|
||||
class VectorDB {
|
||||
// Core database structures
|
||||
private let dimension: Int
|
||||
private var root: KDNodeData?
|
||||
private var tokens: [Token] = []
|
||||
private var totalDocs: Int = 0
|
||||
private var size: Int = 0
|
||||
|
||||
init(dimension: Int) {
|
||||
self.dimension = dimension
|
||||
}
|
||||
|
||||
// Validate token (similar to C implementation)
|
||||
private func isValidToken(_ token: String) -> Bool {
|
||||
let invalidTokens = ["pancake", "author", "radare2"]
|
||||
return !invalidTokens.contains(token)
|
||||
}
|
||||
|
||||
// Find a token in the token list
|
||||
private func findToken(_ tokens: [Token], _ token: String) -> Token? {
|
||||
return tokens.first { $0.token == token }
|
||||
}
|
||||
|
||||
// Compute embedding using TF-IDF approach
|
||||
private func computeEmbedding(_ text: String) -> [Float] {
|
||||
// Zero the embedding vector
|
||||
var embedding = [Float](repeating: 0.0, count: dimension)
|
||||
|
||||
// Tokenize and normalize
|
||||
let words = text
|
||||
.lowercased()
|
||||
.replacingOccurrences(of: "[^a-z0-9\\s]", with: " ", options: .regularExpression)
|
||||
.split(separator: " ")
|
||||
.map { String($0) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
// Local document token frequency
|
||||
var docTokens: [Token] = []
|
||||
for token in words {
|
||||
if let existingToken = findToken(docTokens, token) {
|
||||
let index = docTokens.firstIndex { $0.token == token }!
|
||||
docTokens[index].count += 1
|
||||
} else {
|
||||
docTokens.append(Token(token: token, count: 1, df: 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Increment total documents
|
||||
totalDocs += 1
|
||||
|
||||
// Update global document frequencies
|
||||
for docToken in docTokens {
|
||||
if let globalToken = findToken(self.tokens, docToken.token) {
|
||||
if isValidToken(docToken.token) {
|
||||
let index = self.tokens.firstIndex { $0.token == docToken.token }!
|
||||
self.tokens[index].count += 1
|
||||
self.tokens[index].df += 1
|
||||
}
|
||||
} else {
|
||||
self.tokens.append(Token(token: docToken.token, count: docToken.count, df: 1))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute TF-IDF and update embedding
|
||||
for docToken in docTokens {
|
||||
// Compute term frequency
|
||||
let tf = 1 + log(Float(docToken.count))
|
||||
|
||||
// Find global token to get document frequency
|
||||
let dfValue = findToken(self.tokens, docToken.token)?.df ?? 1
|
||||
|
||||
// Compute inverse document frequency
|
||||
let idf = log(Float(totalDocs + 1) / Float(dfValue + 1)) + 1
|
||||
|
||||
// Compute weight
|
||||
let weight = tf * idf
|
||||
|
||||
// Hash-based index assignment
|
||||
let hash = simpleHash(docToken.token)
|
||||
let index = hash % dimension
|
||||
|
||||
// Add weighted value to embedding
|
||||
embedding[index] += weight
|
||||
}
|
||||
|
||||
// L2 Normalize the embedding
|
||||
return normalizeVector(embedding)
|
||||
}
|
||||
|
||||
// Simple string hash function
|
||||
private func simpleHash(_ str: String) -> Int {
|
||||
var hash = 0
|
||||
for char in str.unicodeScalars {
|
||||
hash = ((hash << 5) - hash) + Int(char.value)
|
||||
hash = hash & hash // Convert to 32-bit integer
|
||||
}
|
||||
return abs(hash)
|
||||
}
|
||||
|
||||
// Normalize vector to unit length
|
||||
private func normalizeVector(_ vec: [Float]) -> [Float] {
|
||||
let normSq = vec.reduce(0) { $0 + $1 * $1 }
|
||||
if normSq > 0 {
|
||||
let norm = sqrt(normSq)
|
||||
return vec.map { $0 / norm }
|
||||
}
|
||||
return vec
|
||||
}
|
||||
|
||||
// Compute squared Euclidean distance
|
||||
private func squaredDistance(_ a: [Float], _ b: [Float]) -> Float {
|
||||
guard a.count == b.count else { return Float.greatestFiniteMagnitude }
|
||||
return zip(a, b).reduce(0) { $0 + pow($1.0 - $1.1, 2) }
|
||||
}
|
||||
|
||||
// Recursive KD-Tree insertion
|
||||
private func insertRecursive(_ node: KDNodeData?, point: [Float], text: String, depth: Int) -> KDNodeData {
|
||||
guard let currentNode = node else {
|
||||
return KDNodeData(point: point, text: text, splitDim: depth % dimension)
|
||||
}
|
||||
|
||||
let axis = currentNode.splitDim
|
||||
if point[axis] < currentNode.point[axis] {
|
||||
currentNode.left = insertRecursive(currentNode.left, point: point, text: text, depth: depth + 1)
|
||||
} else {
|
||||
currentNode.right = insertRecursive(currentNode.right, point: point, text: text, depth: depth + 1)
|
||||
}
|
||||
|
||||
return currentNode
|
||||
}
|
||||
|
||||
// Insert a document into the vector database
|
||||
func insert(_ text: String) {
|
||||
guard !text.isEmpty else { return }
|
||||
|
||||
// Compute embedding
|
||||
let embedding = computeEmbedding(text)
|
||||
|
||||
// Insert into KD-Tree
|
||||
root = insertRecursive(root, point: embedding, text: text, depth: 0)
|
||||
size += 1
|
||||
}
|
||||
|
||||
// K-Nearest Neighbors search
|
||||
func query(_ text: String, k: Int) -> [String] {
|
||||
guard !text.isEmpty, root != nil else { return [] }
|
||||
|
||||
// Compute query embedding
|
||||
let queryEmbedding = computeEmbedding(text)
|
||||
|
||||
// Initialize result set
|
||||
var results: [KNNResult] = []
|
||||
|
||||
// Recursive KNN search
|
||||
func searchRecursive(_ node: KDNodeData?, query: [Float], depth: Int) {
|
||||
guard let node = node else { return }
|
||||
|
||||
// Compute distance and potentially insert into results
|
||||
let distSq = squaredDistance(query, node.point)
|
||||
|
||||
if results.count < k {
|
||||
results.append(KNNResult(node: node, distanceSq: distSq))
|
||||
// Maintain max-heap property
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
} else if distSq < results[0].distanceSq {
|
||||
results[0] = KNNResult(node: node, distanceSq: distSq)
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
}
|
||||
|
||||
// Decide which subtree to search first
|
||||
let axis = node.splitDim
|
||||
let diff = query[axis] - node.point[axis]
|
||||
let first = diff < 0 ? node.left : node.right
|
||||
let second = diff < 0 ? node.right : node.left
|
||||
|
||||
// Recursively search
|
||||
searchRecursive(first, query: query, depth: depth + 1)
|
||||
|
||||
// Potentially search the other subtree
|
||||
if results.count < k || diff * diff < results[0].distanceSq {
|
||||
searchRecursive(second, query: query, depth: depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
searchRecursive(root, query: queryEmbedding, depth: 0)
|
||||
|
||||
// Sort results and return texts
|
||||
return results
|
||||
.sorted { $0.distanceSq < $1.distanceSq }
|
||||
.map { $0.node.text }
|
||||
}
|
||||
|
||||
// Get total number of documents
|
||||
func getSize() -> Int {
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
func main() {
|
||||
// Create a vector database with 64-dimensional embeddings
|
||||
let db = VectorDB(dimension: 64)
|
||||
|
||||
// Insert some sample documents
|
||||
db.insert("The quick brown fox jumps over the lazy dog")
|
||||
db.insert("A quick brown fox is very quick")
|
||||
db.insert("The author of radare2 is name / named pancake")
|
||||
db.insert("The lazy dog sleeps all day")
|
||||
|
||||
// Query for similar documents
|
||||
let results = db.query("who is the author of radare2?", k: 2)
|
||||
print("Similar documents:", results)
|
||||
}
|
||||
|
||||
// Run example
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
PV=3.12
|
||||
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
|
||||
PYTHON?=python${PV}
|
||||
else
|
||||
PYTHON?=python3
|
||||
endif
|
||||
PIP=$(PYTHON) -m pip
|
||||
|
||||
ifeq ($(R2PM_BINDIR),)
|
||||
MISSING RADARE2
|
||||
endif
|
||||
|
||||
LINTED=r2ai/code_block.py
|
||||
LINTED+=r2ai/bubble.py
|
||||
LINTED+=r2ai/const.py
|
||||
LINTED+=r2ai/backend/kobaldcpp.py
|
||||
# LINTED+=r2ai/index.py
|
||||
# LINTED+=r2ai/voice.py
|
||||
# LINTED+=r2ai/anthropic.py
|
||||
r2ai-python py python r2aipy: venv
|
||||
@./r2ai.sh
|
||||
|
||||
large:
|
||||
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
|
||||
|
||||
all.old:
|
||||
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
|
||||
$(PYTHON) main.py || $(MAKE) deps
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv venv
|
||||
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
|
||||
|
||||
deps: venv
|
||||
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
|
||||
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
pip install --force-reinstall -U --no-cache-dir .
|
||||
clean:
|
||||
rm -rf venv
|
||||
rm -rf build
|
||||
find . -name "*.egg-info" -exec rm -rf {} +
|
||||
|
||||
user-install:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
|
||||
|
||||
install: user-install
|
||||
-mkdir -p /usr/local/share/man/man1/r2ai.1
|
||||
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
install-plugin user-install-plugin:
|
||||
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
-rm -f /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
user-uninstall-plugin uninstall-plugin:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
pub:
|
||||
$(PYTHON) -m build
|
||||
twine check dist/*
|
||||
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
|
||||
|
||||
pylint lint cilint:
|
||||
. venv/bin/activate ; pylint $(LINTED)
|
||||
|
||||
lintall:
|
||||
pylint *.py r2ai/*.py
|
||||
|
||||
.PHONY: lint lintall
|
||||
|
||||
deps-global:
|
||||
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# r2ai-python
|
||||
|
||||
This directory contains the original implementation of the AI support for radare2 written in Python.
|
||||
|
||||
It is considered _deprecated_, but it's still useful and aims to be maintained, we recommend using decai or the C rewrite as alternatives.
|
||||
|
||||
The Python implementation consists in a module can be used in 3 different ways:
|
||||
|
||||
* commandline repl using r2pipe to spawn or connect to remote radare2 instances
|
||||
* radare2 core plugin that is instantiated when placing it in the radare2 plugin's directory
|
||||
* Python API to be used for writing your own scripts
|
||||
|
||||
## API Providers
|
||||
|
||||
The Python implementation focused on supporting the LlamaCpp Python module, which is somehow heavy to load in many environments, but it also supports litellm which provides access to many external connectors like ollama, openai, anthropic, gemini, etc.
|
||||
|
||||
## Deprecation Reasons
|
||||
|
||||
There are several reasons why this implementation is considered deprecated and not recommended to use:
|
||||
|
||||
1) It's Python.
|
||||
|
||||
- Aka, it's slow, heavy loads in same process
|
||||
- Your system probably have multiple versions installed
|
||||
- The shell and radare2 python bindings are probably not the same
|
||||
- Required venv to be created (at least an extra 1GB)
|
||||
- Not typed, regressions and bugs appear at runtime
|
||||
|
||||
2) Goto 1
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python3 -m pip install .
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
### Selecting the model
|
||||
|
||||
- List all downloaded models: `-m`
|
||||
- Get a short list of models: `-MM`
|
||||
- Help: `-h`
|
||||
|
||||
**Example selecting a remote models:**
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m anthropic:claude-sonnet-5-5
|
||||
[r2ai:0x00006aa0]> -m openai:gpt-4
|
||||
```
|
||||
|
||||
**Example downloading a free local AI: Mistral 7B v0.2:**
|
||||
|
||||
Launch r2ai, select the model and ask a question. If the model isn't downloaded yet, r2ai will ask you which precise version to download.
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
```
|
||||
|
||||
Then ask your question, and r2ai will automatically download if needed:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> give me a short algorithm to test prime numbers
|
||||
Select TheBloke/Mistral-7B-Instruct-v0.2-GGUF model. See -M and -m flags
|
||||
[?] Quality (smaller is faster):
|
||||
> Small | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
Medium | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
Large | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
See More
|
||||
|
||||
[?] Quality (smaller is faster):
|
||||
> mistral-7b-instruct-v0.2.Q2_K.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_L.gguf | Size: 3.6 GB, Estimated RAM usage: 6.1 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_M.gguf | Size: 3.3 GB, Estimated RAM usage: 5.8 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_S.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q4_0.gguf | Size: 3.8 GB, Estimated RAM usage: 6.3 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_M.gguf | Size: 4.1 GB, Estimated RAM usage: 6.6 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_S.gguf | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
mistral-7b-instruct-v0.2.Q5_0.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_M.gguf | Size: 4.8 GB, Estimated RAM usage: 7.3 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_S.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q6_K.gguf | Size: 5.5 GB, Estimated RAM usage: 8.0 GB
|
||||
mistral-7b-instruct-v0.2.Q8_0.gguf | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
|
||||
[?] Use this model by default? ~/.r2ai.model:
|
||||
> Yes
|
||||
No
|
||||
|
||||
[?] Download to ~/.local/share/r2ai/models? (Y/n): Y
|
||||
```
|
||||
|
||||
**Example selecting a local model served by Ollama**
|
||||
|
||||
Download a model and make it available through Ollama:
|
||||
|
||||
```
|
||||
$ ollama ls
|
||||
NAME ID SIZE MODIFIED
|
||||
codegeex4:latest 867b8e81d038 5.5 GB 23 hours ago
|
||||
```
|
||||
|
||||
Use it from r2ai by prefixing its name with `ollama/`
|
||||
|
||||
```
|
||||
[r2ai:0x00002d30]> -m ollama/codegeex4:latest
|
||||
[r2ai:0x00002d30]> hi
|
||||
Hello! How can I assist you today?
|
||||
```
|
||||
|
||||
### Standard/Auto mode
|
||||
|
||||
The standard mode is invoked by directly asking the question.
|
||||
For the Auto mode, the question **must be prefixed** by `' ` (quote + space). The AI may instruct r2ai to run various commands. Those commands are run on *your host*, so you will be asked to review them before they run.
|
||||
|
||||
Example in "standard" mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> compute 4+5
|
||||
4 + 5 = 9
|
||||
[r2ai:0x00006aa0]> draw me a pancake in ASCII art
|
||||
Sure, here's a simple ASCII pancake:
|
||||
|
||||
_____
|
||||
( )
|
||||
( )
|
||||
-----
|
||||
```
|
||||
|
||||
Example in auto mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]>' Decompile the main
|
||||
[..]
|
||||
r2ai is going to execute the following command on the host
|
||||
Want to edit? (ENTER to validate) pdf @ fcn.000015d0
|
||||
This command will execute on this host: pdf @ fcn.000015d0. Agree? (y/N) y
|
||||
```
|
||||
|
||||
If you wish to edit the command, you can do it inline for short one line commands, or an editor will pop up.
|
||||
|
||||
### r2ai Configuration settings
|
||||
|
||||
List all settings with `-e`
|
||||
|
||||
| Key | Explanation |
|
||||
| ----------- | ------------------------------------- |
|
||||
| debug_level | All verbose messages for level 1. Default is 2 |
|
||||
| auto.max_runs | Maximum number of questions the AI is allowed to ask r2 in auto mode. |
|
||||
| auto.hide_tool_output | By default false, consequently output of r2cmd, run_python etc is shown. Set to `true` to hide those internal messages. |
|
||||
| chat.show_cost | Show the cost of each request to the AI if true |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "r2ai"
|
||||
dynamic = ["readme"]
|
||||
version = "0.8.8"
|
||||
version = "0.9.6"
|
||||
description = "Artificial intelligence tooling for radare2"
|
||||
|
||||
license = {text = "MIT License"}
|
||||
@@ -13,7 +13,7 @@ dependencies = [
|
||||
"rich",
|
||||
"r2pipe",
|
||||
"inquirer",
|
||||
"llama-cpp-python==0.3.1",
|
||||
"llama-cpp-python==0.3.7",
|
||||
"huggingface_hub",
|
||||
"appdirs",
|
||||
"unidecode",
|
||||
@@ -25,7 +25,7 @@ dependencies = [
|
||||
"boto3",
|
||||
"colorama",
|
||||
"textual",
|
||||
"litellm",
|
||||
"litellm>=1.60.5",
|
||||
"numpydoc"
|
||||
]
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -13,10 +13,18 @@ import signal
|
||||
from .spinner import spinner
|
||||
from .completion import create_chat_completion
|
||||
import uuid
|
||||
import time
|
||||
import readline
|
||||
import subprocess
|
||||
import shlex
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
litellm.drop_params = True
|
||||
# litellm.drop_params = True
|
||||
# litellm.set_verbose=True
|
||||
|
||||
ANSI_REGEX = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||
|
||||
SYSTEM_PROMPT_AUTO = """
|
||||
You are a reverse engineer and you are using radare2 to analyze a binary.
|
||||
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
|
||||
@@ -39,8 +47,16 @@ The user will ask questions about the binary and you will respond with the answe
|
||||
- Make sure you call tools and functions correctly.
|
||||
"""
|
||||
|
||||
SYSTEM_PROMPT_AUTO_REASONING = f"""
|
||||
You are an expert reverse engineer.
|
||||
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
|
||||
You can use the available tools to help you answer the user's question.
|
||||
The binary has already been loaded. You can interact with the binary using the r2cmd tool.
|
||||
If you need to run a command in r2 before answering, you can use the r2cmd tool
|
||||
"""
|
||||
|
||||
class ChatAuto:
|
||||
def __init__(self, model, max_tokens = 1024, top_p=0.95, temperature=0.0, interpreter=None, system=None, tools=None, messages=None, tool_choice='auto', llama_instance=None, timeout=60, stream=True, cb=None ):
|
||||
def __init__(self, model, max_tokens = 32000, top_p=0.95, temperature=0.0, interpreter=None, system=None, tools=None, ask_to_execute=True, messages=None, tool_choice='auto', llama_instance=None, timeout=60, stream=True, cb=None, max_runs=100):
|
||||
self.logger = LOGGER
|
||||
self.functions = {}
|
||||
self.tools = []
|
||||
@@ -54,8 +70,44 @@ class ChatAuto:
|
||||
self.system_message = None
|
||||
self.timeout = timeout
|
||||
self.stream = stream
|
||||
if messages and messages[0]['role'] != 'system' and system:
|
||||
self.messages.insert(0, { "role": "system", "content": system })
|
||||
self.cost = 0
|
||||
self.is_reasoning = False
|
||||
self.n_runs = 0
|
||||
self.max_runs = max_runs
|
||||
self._last_response = None
|
||||
self._start_time = time.time()
|
||||
self._last_run_time = time.time()
|
||||
self.ask_to_execute = ask_to_execute
|
||||
self.llama_instance = llama_instance or interpreter.llama_instance if interpreter else None
|
||||
|
||||
init_prompt = ""
|
||||
|
||||
if not self.llama_instance:
|
||||
rmodel = re.sub(r'ft:([^:]+).*', r'\1', self.model)
|
||||
model_info = litellm.get_model_info(rmodel)
|
||||
self.max_tokens = min(self.max_tokens, model_info['max_tokens'])
|
||||
|
||||
if self.model.startswith('openai/o'):
|
||||
self.is_reasoning = True
|
||||
self.max_tokens = model_info['max_tokens']
|
||||
self.timeout = 60 * 60
|
||||
init_commands = self.interpreter.env["auto.init_commands"]
|
||||
|
||||
if init_commands:
|
||||
init_prompt = f"""
|
||||
Here is some information about the binary to get you started:
|
||||
> {init_commands}
|
||||
{r2cmd(init_commands)}
|
||||
"""
|
||||
if self.is_reasoning:
|
||||
if not system and not self.messages[0]['role'] == 'developer':
|
||||
self.messages.insert(0, { "role": "developer", "content": SYSTEM_PROMPT_AUTO_REASONING + init_prompt })
|
||||
self.stream = False
|
||||
else:
|
||||
if messages and messages[0]['role'] != 'system':
|
||||
self.messages.insert(0, { "role": "system", "content": system or SYSTEM_PROMPT_AUTO + init_prompt })
|
||||
|
||||
|
||||
if cb:
|
||||
self.cb = cb
|
||||
else:
|
||||
@@ -71,7 +123,6 @@ class ChatAuto:
|
||||
self.tools.append({ "type": "function", "function": schema })
|
||||
self.functions[tool.__name__] = tool
|
||||
self.tool_choice = tool_choice
|
||||
self.llama_instance = llama_instance or interpreter.llama_instance if interpreter else None
|
||||
#self.tool_end_message = '\nNOTE: The user saw this output, do not repeat it.'
|
||||
|
||||
async def process_tool_calls(self, tool_calls):
|
||||
@@ -96,12 +147,69 @@ class ChatAuto:
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": "Error: Tool not found" , "tool_call_id": tool_call["id"]})
|
||||
continue
|
||||
|
||||
if self.ask_to_execute:
|
||||
# tool_call typically contains:
|
||||
# { 'id': 'toolu_01Hr6XTatRtp7rX54iBu1voC', 'type': 'function', 'function': {'name': 'r2cmd', 'arguments': '{"command": "pdf @ main"}'}}
|
||||
answer = ''
|
||||
will_execute = True # a command (modified or not) will be executed
|
||||
while answer.lower() != 'y':
|
||||
try:
|
||||
command = tool_args.get('command', '')
|
||||
|
||||
if len(command.splitlines()) > 1:
|
||||
print(f'r2ai is going to execute the following command on the host:')
|
||||
print(f'{command}')
|
||||
want_edit = input('Want to edit? (Y/n) ')
|
||||
if want_edit.lower() != 'n':
|
||||
# open editor when several lines
|
||||
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
|
||||
temp_file.write(command)
|
||||
temp_filename = temp_file.name
|
||||
|
||||
# Fallback to 'nano' if EDITOR is not set (I still prefer emacs)
|
||||
editor = os.environ.get('EDITOR', 'nano')
|
||||
subprocess.run(shlex.split(editor + ' ' + temp_filename))
|
||||
with open(temp_filename, "r") as f:
|
||||
new_command = f.read().strip()
|
||||
os.remove(temp_filename)
|
||||
else:
|
||||
self.logger.debug(f'User agrees with not editing the command: {command}')
|
||||
answer = 'y'
|
||||
|
||||
else:
|
||||
# inline short edit
|
||||
print(f'r2ai is going to execute the following command on the host:')
|
||||
print(f'> {command}')
|
||||
|
||||
new_command = input(f"Type a new command or press ENTER to use {command} ") or command
|
||||
|
||||
if answer.lower() != 'y':
|
||||
answer = input(f"\033[91mThis command will execute on this host: {new_command}. Agree? (y/N)\033[0m ")
|
||||
if answer.lower() == 'y':
|
||||
# we need to refresh tool_call and tool_args with edited values
|
||||
tool_args['command'] = new_command
|
||||
tool_call['function']['arguments'] = json.dumps(tool_args)
|
||||
|
||||
except (json.JSONDecodeError, TypeError, AttributeError) as e:
|
||||
# in case tool_call is not formatted as expected, like missing command, arguments, function...
|
||||
self.logger.error(f'Unexpected format for {tool_call}')
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": "User refused to execute the command" , "tool_call_id": tool_call["id"]})
|
||||
# error case: we'll skip the tool
|
||||
will_execute = False
|
||||
break
|
||||
|
||||
if not will_execute:
|
||||
self.logger.debug(f'Skipping tool={tool_call}')
|
||||
continue
|
||||
|
||||
self.logger.debug(f'Executing tool id={tool_call["id"]}, name={tool_name}, arguments={tool_args}')
|
||||
self.cb('tool_call', { "id": tool_call["id"], "function": { "name": tool_name, "arguments": tool_args } })
|
||||
if asyncio.iscoroutinefunction(self.functions[tool_name]):
|
||||
tool_response = await self.functions[tool_name](**tool_args)
|
||||
else:
|
||||
tool_response = self.functions[tool_name](**tool_args)
|
||||
self.cb('tool_response', { "id": tool_call["id"] + '_response', "content": tool_response })
|
||||
if self.interpreter.env["auto.hide_tool_output"] == "false":
|
||||
self.cb('tool_response', { "id": tool_call["id"] + '_response', "content": tool_response })
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": ANSI_REGEX.sub('', tool_response), "tool_call_id": tool_call["id"]})
|
||||
|
||||
return await self.get_completion()
|
||||
@@ -111,6 +219,7 @@ class ChatAuto:
|
||||
msgs = []
|
||||
parts = []
|
||||
current_message = { "role": "assistant", "content": "", "tool_calls": [] }
|
||||
first_chunk = True
|
||||
async for chunk in resp:
|
||||
delta = None
|
||||
choice = chunk.choices[0]
|
||||
@@ -138,6 +247,9 @@ class ChatAuto:
|
||||
m = None
|
||||
done = False
|
||||
if delta.content is not None:
|
||||
if first_chunk:
|
||||
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
|
||||
first_chunk = False
|
||||
m = delta.content
|
||||
if m is not None:
|
||||
current_message['content'] += m
|
||||
@@ -149,19 +261,22 @@ class ChatAuto:
|
||||
self.messages.append(current_message)
|
||||
if len(current_message['tool_calls']) == 0:
|
||||
del current_message['tool_calls']
|
||||
self.show_cost()
|
||||
else:
|
||||
self.show_cost()
|
||||
await self.process_tool_calls(current_message['tool_calls'])
|
||||
|
||||
return current_message
|
||||
|
||||
async def process_response(self, resp):
|
||||
content = resp.choices[0].message.content
|
||||
tool_calls = []
|
||||
|
||||
current_message = { 'role': 'assistant', 'content': content or '', 'tool_calls': [] }
|
||||
for tool_call in resp.choices[0].message.tool_calls or []:
|
||||
for i, tool_call in enumerate(resp.choices[0].message.tool_calls or []):
|
||||
current_message['tool_calls'].append({
|
||||
"id": tool_call.id,
|
||||
"type": "function",
|
||||
"index": tool_call.index,
|
||||
"index": i,
|
||||
"function": {
|
||||
"name": tool_call.function.name,
|
||||
"arguments": tool_call.function.arguments
|
||||
@@ -180,11 +295,17 @@ class ChatAuto:
|
||||
current_message['tool_calls'].append({ "id": resp.id, "function": { "name": tool_call['name'], "arguments": json.dumps(args) } })
|
||||
except Exception:
|
||||
pass
|
||||
if len(current_message['tool_calls']) > 0:
|
||||
self.messages.append(current_message)
|
||||
await self.process_tool_calls(current_message['tool_calls'])
|
||||
|
||||
self.messages.append(current_message)
|
||||
if current_message['content'] != "":
|
||||
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
|
||||
self.cb('message', { "content": current_message['content'], "id": 'message_' + resp.id, 'done': True })
|
||||
|
||||
if len(current_message['tool_calls']) == 0:
|
||||
del current_message['tool_calls']
|
||||
self.show_cost()
|
||||
else:
|
||||
self.show_cost()
|
||||
await self.process_tool_calls(current_message['tool_calls'])
|
||||
|
||||
return current_message
|
||||
|
||||
@@ -202,23 +323,65 @@ class ChatAuto:
|
||||
"max_tokens": self.max_tokens,
|
||||
"stream": stream,
|
||||
}
|
||||
res = create_chat_completion(self.interpreter, messages=self.messages, tools=self.tools, **args)
|
||||
if self.interpreter.env["chat.rawdog"] == "true":
|
||||
res = create_chat_completion(self.interpreter, messages=self.messages, tools=self.tools, **args)
|
||||
else:
|
||||
res = self.llama_instance.create_chat_completion(self.messages, tools=self.tools, **args)
|
||||
if args['stream']:
|
||||
return self.async_response_generator(res)
|
||||
else:
|
||||
return ModelResponse(**next(res))
|
||||
self.logger.debug('chat completion')
|
||||
return await acompletion(
|
||||
additional_args = {}
|
||||
if self.is_reasoning:
|
||||
additional_args['reasoning_effort'] = self.interpreter.env["chat.reasoning_effort"]
|
||||
else:
|
||||
additional_args['temperature'] = self.temperature
|
||||
if self.interpreter.env["chat.openai_store"] == "true":
|
||||
additional_args['store'] = True
|
||||
if not self.check_max():
|
||||
additional_args['tools'] = self.tools
|
||||
additional_args['tool_choice'] = self.tool_choice
|
||||
compl = await acompletion(
|
||||
model=self.model,
|
||||
messages=self.messages,
|
||||
timeout=self.timeout,
|
||||
tools=self.tools,
|
||||
tool_choice=self.tool_choice,
|
||||
temperature=self.temperature,
|
||||
top_p=self.top_p,
|
||||
max_tokens=self.max_tokens,
|
||||
max_completion_tokens=self.max_tokens,
|
||||
**additional_args,
|
||||
stream=stream,
|
||||
)
|
||||
return compl
|
||||
|
||||
def check_max(self):
|
||||
if self.max_runs > 0 and self.n_runs >= self.max_runs - 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_cost(self):
|
||||
def format_time(seconds):
|
||||
minutes = int(seconds // 60)
|
||||
remaining_seconds = int(seconds % 60)
|
||||
if minutes > 0:
|
||||
return f"{minutes}m {remaining_seconds}s"
|
||||
return f"{remaining_seconds}s"
|
||||
self.n_runs += 1
|
||||
run_time = format_time(time.time() - self._last_run_time)
|
||||
self._last_run_time = time.time()
|
||||
total_time = format_time(time.time() - self._start_time)
|
||||
|
||||
if self.interpreter.env["chat.show_cost"] == "true" and self.n_runs > 0:
|
||||
run_cost = 0
|
||||
if not self.llama_instance:
|
||||
run_cost = litellm.completion_cost(completion_response=self._last_response, messages=self.messages, model=self.model)
|
||||
self.cost += run_cost
|
||||
cb('usage', {
|
||||
"model": self.model,
|
||||
"run_cost": run_cost,
|
||||
"total_cost": self.cost,
|
||||
"n_runs": self.n_runs,
|
||||
"max_runs": self.max_runs,
|
||||
"run_time": run_time,
|
||||
"total_time": total_time
|
||||
})
|
||||
|
||||
async def get_completion(self):
|
||||
stream = self.stream
|
||||
@@ -234,11 +397,15 @@ class ChatAuto:
|
||||
for retry_count in range(max_retries):
|
||||
try:
|
||||
response = await self.attempt_completion()
|
||||
self._last_response = response
|
||||
self.logger.debug(f'chat completion {response}')
|
||||
res = None
|
||||
if stream:
|
||||
return await self.process_streaming_response(response)
|
||||
res = await self.process_streaming_response(response)
|
||||
else:
|
||||
return await self.process_response(response)
|
||||
res = await self.process_response(response)
|
||||
|
||||
return res
|
||||
except Exception as e:
|
||||
self.logger.error(f'Error getting completion: {e}')
|
||||
if not _should_retry(getattr(e, 'status_code', None)) or retry_count == max_retries - 1:
|
||||
@@ -273,9 +440,19 @@ def cb(type, data):
|
||||
sys.stdout.write(data['content'])
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
# builtins.print(data['content'])
|
||||
elif type == 'message' and data['done']:
|
||||
elif type == 'usage':
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
sys.stdout.write(f'\x1b[1;34m{data["model"]} | total: ${float(data["total_cost"]):.10f} | run: ${float(data["run_cost"]):.10f} | {data["n_runs"]} / {data["max_runs"]} | {data["run_time"]} / {data["total_time"]}\x1b[0m')
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
elif type == 'message' and data['done']:
|
||||
if data['content']:
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write(data['content'])
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
builtins.print()
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
raise KeyboardInterrupt
|
||||
@@ -295,7 +472,21 @@ def chat(interpreter, **kwargs):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
chat_auto = ChatAuto(model, max_tokens=int(interpreter.env["llm.maxtokens"]), top_p=float(interpreter.env["llm.top_p"]), temperature=float(interpreter.env["llm.temperature"]), interpreter=interpreter, system=SYSTEM_PROMPT_AUTO, tools=tools, messages=messages, tool_choice=tool_choice, llama_instance=interpreter.llama_instance, cb=cb)
|
||||
chat_auto = ChatAuto(
|
||||
model,
|
||||
max_tokens=int(interpreter.env["llm.maxtokens"]),
|
||||
top_p=float(interpreter.env["llm.top_p"]),
|
||||
temperature=float(interpreter.env["llm.temperature"]),
|
||||
interpreter=interpreter,
|
||||
tools=tools,
|
||||
ask_to_execute=interpreter.env["auto.ask_to_execute"] == "true",
|
||||
messages=messages,
|
||||
tool_choice=tool_choice,
|
||||
llama_instance=interpreter.llama_instance,
|
||||
stream=interpreter.env["chat.stream"] == "true",
|
||||
max_runs=int(interpreter.env["auto.max_runs"]),
|
||||
cb=cb
|
||||
)
|
||||
|
||||
original_handler = signal.getsignal(signal.SIGINT)
|
||||
|
||||
@@ -317,4 +508,4 @@ def chat(interpreter, **kwargs):
|
||||
finally:
|
||||
signal.signal(signal.SIGINT, original_handler)
|
||||
spinner.stop()
|
||||
litellm.in_memory_llm_clients_cache.clear()
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -14,6 +14,15 @@ CLIPPY_BEGIN = """
|
||||
|
||||
CLIPPY_END = """`-----------------------------------------------------------------------"""
|
||||
|
||||
PICO_BEGIN = r"""
|
||||
__ __
|
||||
(o |_| o)_______
|
||||
| _____________)
|
||||
\ / .
|
||||
\ / |\
|
||||
\__________/ | \
|
||||
.----------------------------------------------------------' '---------"""
|
||||
|
||||
USER_BEGIN = """ |\\
|
||||
| \\
|
||||
.--' '--"""
|
||||
@@ -48,6 +57,11 @@ def response_begin():
|
||||
"""
|
||||
print(CLIPPY_BEGIN + getpad() + ".")
|
||||
|
||||
def pico_begin():
|
||||
"""Print the beginning of the Pico le Croco response
|
||||
"""
|
||||
print(PICO_BEGIN + getpad() + ".")
|
||||
|
||||
def response_end():
|
||||
"""Print the end of the clippy response
|
||||
"""
|
||||
File renamed without changes.
File renamed without changes.
@@ -1007,4 +1007,4 @@ def create_chat_completion(self, **kwargs):
|
||||
|
||||
def create_completion(self, **kwargs):
|
||||
for item in self.create_completion(**kwargs):
|
||||
yield item['choices'][0]['text']
|
||||
yield item['choices'][0]['text']
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -88,16 +88,19 @@ def ddg(m):
|
||||
return f"Considering:\n```{res}\n```\n"
|
||||
|
||||
def is_litellm_model(model):
|
||||
from litellm import models_by_provider
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
import litellm
|
||||
provider = None
|
||||
model_name = None
|
||||
if model.startswith ("/"):
|
||||
return False
|
||||
if ":" in model:
|
||||
provider, model_name = model.split(":")
|
||||
elif "/" in model:
|
||||
provider, model_name = model.split("/")
|
||||
if provider in models_by_provider and model_name in models_by_provider[provider]:
|
||||
provider = None
|
||||
if "/" in model:
|
||||
provider = model.split("/")[0]
|
||||
elif ":" in model:
|
||||
provider = model.split(":")[0]
|
||||
if provider in litellm.LITELLM_CHAT_PROVIDERS:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -158,7 +161,16 @@ class Interpreter:
|
||||
self.env["chat.reply"] = "false"
|
||||
self.env["chat.code"] = "true"
|
||||
self.env["chat.rawdog"] = "false"
|
||||
|
||||
self.env["chat.reasoning_effort"] = "high"
|
||||
self.env["chat.stream"] = "true"
|
||||
self.env["chat.show_cost"] = "false"
|
||||
self.env["chat.drop_params"] = "false"
|
||||
self.env["chat.openai_store"] = "false"
|
||||
self.env["auto.max_runs"] = "100"
|
||||
self.env["auto.hide_tool_output"] = "false"
|
||||
self.env["auto.init_commands"] = "aaa;iI;afl"
|
||||
self.env["auto.ask_to_execute"] = "true" # ask to run tools such as r2cmd, run_python etc
|
||||
|
||||
self.env.add_callback("debug_level", lambda val: LOGGER.setLevel(int(val) * 10))
|
||||
|
||||
# No active block to start
|
||||
@@ -284,14 +296,14 @@ class Interpreter:
|
||||
self.logger.debug(message)
|
||||
|
||||
# print(message)
|
||||
# Local model
|
||||
# Local model -- TODO: assume local models don't have : in the name?
|
||||
if (
|
||||
not self.model.startswith("openai:") and
|
||||
not self.model.startswith("openapi:") and
|
||||
not self.model.startswith("google:") and
|
||||
not self.model.startswith("kobaldcpp") and
|
||||
not self.model.startswith("bedrock:") and
|
||||
self.llama_instance is None
|
||||
and not is_litellm_model(self.model)
|
||||
not is_litellm_model(self.model)
|
||||
):
|
||||
self.logger = LOGGER.getChild(f"local:{self.model}")
|
||||
# Find or install Code-Llama
|
||||
@@ -378,6 +390,12 @@ class Interpreter:
|
||||
response = None
|
||||
if self.auto_run:
|
||||
from . import auto
|
||||
import litellm
|
||||
if self.env["chat.drop_params"] == "true":
|
||||
litellm.drop_params = True
|
||||
else:
|
||||
litellm.drop_params = False
|
||||
|
||||
if(is_litellm_model(self.model)):
|
||||
response = auto.chat(self)
|
||||
else:
|
||||
@@ -417,14 +435,34 @@ class Interpreter:
|
||||
# {"role": "user", "content": "Compose a poem that explains the concept of recursion in programming."}
|
||||
# ]
|
||||
from litellm import completion as litellm_completion
|
||||
completion = litellm_completion(
|
||||
model=self.model.replace(":", "/"),
|
||||
messages=self.messages,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
stop=self.terminator,
|
||||
)
|
||||
completion = None
|
||||
if self.model.startswith("ollama/"):
|
||||
completion = litellm_completion(
|
||||
model=self.model,
|
||||
messages=self.messages,
|
||||
drop_params=True,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
elif self.model.startswith("mistral/"):
|
||||
completion = litellm_completion(
|
||||
drop_params=True,
|
||||
model=self.model.replace(":", "/"),
|
||||
messages=self.messages,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
else:
|
||||
completion = litellm_completion(
|
||||
model=self.model.replace(":", "/"),
|
||||
messages=self.messages,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
|
||||
response = completion.choices[0].message.content
|
||||
if "content" in self.messages[-1]:
|
||||
last_message = self.messages[-1]["content"]
|
||||
@@ -454,13 +492,16 @@ class Interpreter:
|
||||
}
|
||||
if self.env["chat.rawdog"] == "true":
|
||||
from .completion import messages_to_prompt
|
||||
prompt = messages_to_prompt(self, messages)
|
||||
allmsg = messages.copy()
|
||||
allmsg.insert(0, ChatMessage(role=MessageRole.SYSTEM, content=self.system_message))
|
||||
prompt = messages_to_prompt(self, allmsg)
|
||||
response = self.llama_instance(prompt, **chat_args)
|
||||
else:
|
||||
all_messages = messages.copy()
|
||||
all_messages.insert(0, {"role": "system", "content": self.system_message})
|
||||
response = self.llama_instance.create_chat_completion(all_messages, **chat_args)
|
||||
except Exception as err:
|
||||
traceback.print_exc()
|
||||
print(Exception, err)
|
||||
if Ginterrupted:
|
||||
Ginterrupted = False
|
||||
@@ -469,7 +510,7 @@ class Interpreter:
|
||||
if response is None:
|
||||
print("No response")
|
||||
ctxwindow = int(self.env["llm.window"])
|
||||
self.llama_instance = new_get_hf_llm(self, self.model, False, ctxwindow)
|
||||
self.llama_instance = new_get_hf_llm(self, self.model, ctxwindow)
|
||||
return
|
||||
# Initialize message, function call trackers, and active block
|
||||
self.messages.append({})
|
||||
@@ -496,7 +537,7 @@ class Interpreter:
|
||||
delta = {"content": text}
|
||||
elif "content" in chunk['choices'][0]['delta']:
|
||||
delta = {"content": chunk['choices'][0]['delta']['content']}
|
||||
# Accumulate deltas into the last message in messages
|
||||
# Accumulate deltas into the last message in messages
|
||||
if delta:
|
||||
self.messages[-1] = merge_deltas(self.messages[-1], delta)
|
||||
if self.env["chat.live"] != "true":
|
||||
File renamed without changes.
@@ -7,7 +7,7 @@ import builtins
|
||||
import traceback
|
||||
import appdirs
|
||||
import argparse
|
||||
|
||||
import litellm
|
||||
from r2ai.repl import r2ai_singleton
|
||||
from r2ai.utils import slurp
|
||||
from r2ai.repl import runline, r2ai_repl, help_message
|
||||
@@ -69,6 +69,7 @@ def run_rcfile_once(ai):
|
||||
def main(args, commands, dorepl=True):
|
||||
|
||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
||||
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
|
||||
|
||||
try:
|
||||
r2aihome = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -97,6 +98,17 @@ def main(args, commands, dorepl=True):
|
||||
os.environ["OPENAI_API_KEY"] = apikey
|
||||
print("[R2AI] OpenAI API key loaded from ~/.r2ai.openai-key", file=sys.stderr)
|
||||
|
||||
r2_mistral_file = os.path.join(home_dir, ".r2ai.mistral-key")
|
||||
if os.path.isfile(r2_mistral_file):
|
||||
apikey = slurp(r2_mistral_file).strip()
|
||||
os.environ["MISTRAL_API_KEY"] = apikey
|
||||
print("[R2AI] Mistral API key loaded from ~/.r2ai.mistral-key", file=sys.stderr)
|
||||
|
||||
r2_gemini_file = os.path.join(home_dir, ".r2ai.gemini-key")
|
||||
if os.path.isfile(r2_gemini_file):
|
||||
apikey = slurp(r2_gemini_file).strip()
|
||||
os.environ["GEMINI_API_KEY"] = apikey
|
||||
print("[R2AI] Gemini API key loaded from ~/.r2ai.gemini-key", file=sys.stderr)
|
||||
|
||||
r2_anthropic_file = os.path.join(home_dir, ".r2ai.anthropic-key")
|
||||
if os.path.isfile(r2_anthropic_file):
|
||||
@@ -159,4 +171,4 @@ def run():
|
||||
parser.add_argument("-c", "--command", action="append",
|
||||
help="Command to be executed. Can be passed multiple times.")
|
||||
runrepl, args = massage_args(parser.parse_args())
|
||||
main(args, args.command, runrepl)
|
||||
main(args, args.command, runrepl)
|
||||
File renamed without changes.
@@ -47,6 +47,7 @@ Code:
|
||||
-m FaradayDotDev/llama-3-8b-Instruct-GGUF
|
||||
-m second-state/Mistral-Nemo-Instruct-2407-GGUF
|
||||
-m bartowski/gemma-2-9b-it-GGUF
|
||||
-m mradermacher/salamandra-7b-instruct-aina-hack-GGUF
|
||||
Functionary:
|
||||
-m meetkai/functionary-small-v3.2-GGUF
|
||||
-m mradermacher/granite-20b-functioncalling-i1-GGUF
|
||||
@@ -61,7 +62,7 @@ Uncensored:
|
||||
Remote:
|
||||
-m openapi:http://localhost:5001
|
||||
-m openai:gpt-4
|
||||
-m anthropic:claude-3-5-sonnet-20241022
|
||||
-m anthropic:claude-sonnet-5-5
|
||||
-m bedrock:anthropic.claude-3-5-sonnet-20241022-v1
|
||||
-m kobaldcpp:http://localhost:5001
|
||||
"""
|
||||
@@ -73,24 +74,22 @@ KobaldCpp:
|
||||
-m kobaldcpp:http://localhost:5001
|
||||
OpenAPI:
|
||||
-m openapi:http://localhost:5001
|
||||
OpenAPI (custom model):
|
||||
-m openai:http://localhost:5001:gpt-4
|
||||
OpenAI:
|
||||
-m openai:gpt-3.5-turbo
|
||||
-m openai:gpt-4
|
||||
-m openai:gpt-4-1106-preview
|
||||
Anthropic:
|
||||
-m anthropic:claude-2.1
|
||||
-m anthropic:claude-3-haiku-20240307
|
||||
-m anthropic:claude-3-opus-20240229
|
||||
-m anthropic:claude-3-sonnet-20240229
|
||||
-m anthropic:claude-3-5-sonnet-20241022
|
||||
-m anthropic:claude-opus-4-8
|
||||
-m anthropic:claude-sonnet-5-5
|
||||
-m anthropic:claude-haiku-4-5-20251001
|
||||
groq:
|
||||
-m groq:gemma-7b-it
|
||||
-m groq:llama2-70b-4096
|
||||
-m groq:mixtral-8x7b-32768
|
||||
Google:
|
||||
-m google:gemini-1.0-pro
|
||||
-m google:gemini-1.5-flash
|
||||
-m google:gemini-1.5-pro-latest
|
||||
Functionary:
|
||||
-m meetkai/functionary-7b-v1.4-GGUF
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,22 @@
|
||||
"""Entrypoint for the r2ai plugin and repl."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import builtins
|
||||
import traceback
|
||||
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
sys.path.insert(0, parent_dir)
|
||||
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
|
||||
try:
|
||||
venv_dir = os.path.join(parent_dir, 'venv')
|
||||
if os.path.exists(venv_dir):
|
||||
site_packages = os.path.join(venv_dir, 'lib', 'python{}.{}'.format(*sys.version_info[:2]), 'site-packages')
|
||||
if os.path.exists(site_packages):
|
||||
sys.path.insert(0, site_packages)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import r2lang
|
||||
from r2ai.main import r2ai_singleton, run_rcfile_once, runline, help_message
|
||||
|
||||
File renamed without changes.
@@ -34,7 +34,7 @@ def r2_cmd(x):
|
||||
res = None
|
||||
if r2 is not None:
|
||||
oc = r2.cmd('e scr.color').strip()
|
||||
r2.cmd('e scr.color=0')
|
||||
r2.cmd('e scr.color=3')
|
||||
res = r2.cmd(x)
|
||||
r2.cmd('e scr.color=' + oc)
|
||||
return res
|
||||
@@ -68,6 +68,7 @@ help_message = """Usage: r2ai [-option] ([query] | [script.py])
|
||||
r2ai -q quit/exit/^C
|
||||
r2ai -L show chat logs (See -Lj for json)
|
||||
r2ai -L-[N] delete the last (or N last messages from the chat history)
|
||||
r2ai -p1c0 enter the Pico le Croco assistant mode
|
||||
r2ai -repl enter the repl (only when running via r2pipe)
|
||||
r2ai -r [sysprompt] define the role of the conversation
|
||||
r2ai -r2 enter the r2clippy assistant mode
|
||||
@@ -166,11 +167,20 @@ def slurp_until(endword):
|
||||
text += line
|
||||
return text
|
||||
|
||||
def list_models():
|
||||
print("ollama/llama3:latest")
|
||||
print("ollama/granite-code:8b")
|
||||
print("anthropic/claude-sonnet-5-5")
|
||||
print("openai/gpt-4o")
|
||||
print("mistral/mistral-tiny")
|
||||
|
||||
def set_model(model):
|
||||
ai = ais[0].ai
|
||||
if ai.model != model:
|
||||
ai.llama_instance = None
|
||||
ai.model = model
|
||||
if autoai:
|
||||
autoai.model = model
|
||||
ai.env["llm.model"] = ai.model
|
||||
set_default_model(ai.model)
|
||||
|
||||
@@ -229,6 +239,10 @@ def runline(ai, usertext):
|
||||
list_downloaded_models()
|
||||
elif usertext.startswith("-m-"):
|
||||
delete_downloaded_model(usertext[3:])
|
||||
elif usertext.startswith("r2ai"):
|
||||
print("ERROR: Cant run r2ai from r2ai, you must install the C plugin and run this command from radare2")
|
||||
elif usertext == "-m?":
|
||||
list_models()
|
||||
elif usertext.startswith("-m"):
|
||||
words = usertext.split(" ")
|
||||
if len(words) > 1:
|
||||
@@ -237,6 +251,8 @@ def runline(ai, usertext):
|
||||
print(ai.model)
|
||||
elif usertext == "reset" or usertext.startswith("-R"):
|
||||
ai.reset()
|
||||
if autoai:
|
||||
autoai.reset()
|
||||
elif usertext.startswith("-j"):
|
||||
q = usertext[2:].strip()
|
||||
query = f"Please respond using ONLY in JSON with the following fields if relevant: topic, array of key words, location, url, name, description, target, amounts and other details if necessary without providing any response. Question: \"{q}\""
|
||||
@@ -276,6 +292,13 @@ def runline(ai, usertext):
|
||||
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
|
||||
ai.env["chat.bubble"] = "true"
|
||||
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/r2clippy.txt")
|
||||
elif usertext == "-p1c0":
|
||||
ai.env["data.use"] = "true"
|
||||
ai.env["data.hist"] = "true"
|
||||
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
|
||||
ai.env["chat.bubble"] = "true"
|
||||
ai.env["chat.picolecroco"] = "true"
|
||||
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/picolecroco.txt")
|
||||
elif usertext.startswith("-ed"):
|
||||
editor = "vim" # Defaults to the only real editor
|
||||
if ai.env["user.editor"] != "":
|
||||
@@ -326,15 +349,23 @@ def runline(ai, usertext):
|
||||
sys.exit(0)
|
||||
elif usertext.startswith("-W"):
|
||||
if len(usertext) > 2:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
try:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
except:
|
||||
print("Use -w [port-number]")
|
||||
return
|
||||
start_http_server(ai, runline2, True)
|
||||
elif usertext.startswith("-w"):
|
||||
if len(usertext) > 2:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
try:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
except:
|
||||
print("Use -w [port-number]")
|
||||
return
|
||||
start_http_server(ai, runline2, False)
|
||||
elif usertext.startswith("-s"):
|
||||
ai.runline2 = runline2
|
||||
@@ -440,7 +471,8 @@ def runline(ai, usertext):
|
||||
if input() == 'q':
|
||||
break
|
||||
else:
|
||||
n = int(usertext[1:])
|
||||
safe_int = lambda x: int(x) if str(x).isdigit() else 0
|
||||
n = safe_int(usertext[1:])
|
||||
if n > 0:
|
||||
items = tab_list()
|
||||
runline(ai, items[n - 1])
|
||||
@@ -464,6 +496,7 @@ def runline(ai, usertext):
|
||||
elif usertext.startswith("' "):
|
||||
if not autoai:
|
||||
autoai = Interpreter()
|
||||
autoai.env = ai.env
|
||||
autoai.auto_run = True
|
||||
|
||||
autoai.chat(usertext[2:])
|
||||
@@ -516,7 +549,10 @@ def r2ai_repl(ai):
|
||||
break
|
||||
else:
|
||||
bubble.query(usertext)
|
||||
bubble.response_begin()
|
||||
if ai.env["chat.picolecroco"] == "true":
|
||||
bubble.pico_begin()
|
||||
else:
|
||||
bubble.response_begin()
|
||||
if runline(ai, usertext) == "q":
|
||||
break
|
||||
bubble.response_end()
|
||||
File renamed without changes.
@@ -141,10 +141,13 @@ def tab_init():
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
readline.set_completer(completer.complete)
|
||||
readline.set_completer_delims('\t\n;')
|
||||
readline.set_completion_display_matches_hook(completer.display_matches)
|
||||
if readline.__doc__.find("GNU") != -1:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
else:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
try:
|
||||
readline.set_completer(completer.complete)
|
||||
readline.set_completer_delims('\t\n;')
|
||||
readline.set_completion_display_matches_hook(completer.display_matches)
|
||||
if readline.__doc__.find("GNU") != -1:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
else:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
except Exception as e:
|
||||
pass
|
||||
File renamed without changes.
@@ -32,6 +32,7 @@ def r2cmd(command: str):
|
||||
The output of the r2 command
|
||||
"""
|
||||
r2 = get_r2_inst()
|
||||
r2.cmd('e scr.color=3')
|
||||
if command.startswith('r2 '):
|
||||
return "You are already in r2!"
|
||||
cmd = '{"cmd":' + json.dumps(command) + '}'
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -26,10 +26,8 @@ def merge_deltas(original, delta):
|
||||
return original
|
||||
|
||||
def slurp(f):
|
||||
fd = open(f, errors="ignore")
|
||||
data = fd.read()
|
||||
fd.close()
|
||||
return str(data)
|
||||
with open(f, errors="ignore") as fd:
|
||||
return str(fd.read())
|
||||
|
||||
def dump(f, x):
|
||||
fd = open(f, "w")
|
||||
@@ -66,4 +64,4 @@ def filter_print(*args, **kwargs):
|
||||
new += str(a)
|
||||
_args.append(new)
|
||||
|
||||
print(*_args, **kwargs)
|
||||
print(*_args, **kwargs)
|
||||
@@ -50,8 +50,16 @@ def stt(seconds, lang):
|
||||
return
|
||||
tts("(r2ai) listening for 5s... ", "digues?", lang)
|
||||
print(f"DEVICE IS {device}")
|
||||
os.system("rm -f .audiomsg.wav")
|
||||
rc = os.system(f"ffmpeg -f avfoundation -t 5 -i '{device}' .audiomsg.wav > /dev/null 2>&1")
|
||||
try:
|
||||
os.remove(".audiomsg.wav")
|
||||
except OSError:
|
||||
pass
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-f", "avfoundation", "-t", "5", "-i", device, ".audiomsg.wav"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
rc = result.returncode
|
||||
if rc != 0:
|
||||
tts("(r2ai)", "cannot record from microphone. missing permissions in terminal?", lang)
|
||||
return
|
||||
@@ -60,7 +68,10 @@ def stt(seconds, lang):
|
||||
result = model.transcribe(".audiomsg.wav")
|
||||
else:
|
||||
result = model.transcribe(".audiomsg.wav", language=lang)
|
||||
os.system("rm -f .audiomsg.wav")
|
||||
try:
|
||||
os.remove(".audiomsg.wav")
|
||||
except OSError:
|
||||
pass
|
||||
tts("(r2ai)", "ok", lang)
|
||||
text = result["text"].strip()
|
||||
if text == "you":
|
||||
File renamed without changes.
@@ -3,12 +3,17 @@ R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
R2_BINDIR=$(shell r2 -H R2_BINDIR)
|
||||
|
||||
install:
|
||||
# rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
# ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
mkdir -p $(R2_BINDIR)
|
||||
cp -f r2ai-server $(R2_BINDIR)/r2ai-server || echo Maybe use user-install instead?
|
||||
|
||||
uninstall:
|
||||
rm -f $(R2_BINDIR)/r2ai-server
|
||||
|
||||
user-install:
|
||||
mkdir -p $(R2PM_BINDIR)
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
ln -fs $(PWD)/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
|
||||
user-uninstall:
|
||||
@@ -0,0 +1,25 @@
|
||||
# r2ai-server
|
||||
|
||||
Start ollama, llamacpp, llamafile, r2ai, kobaldcpp and other language model webservers using a the same syntax for all of them to simplify its launching and setup.
|
||||
|
||||
* Use -l to list the implementations available
|
||||
|
||||
You can install more via r2pm
|
||||
|
||||
* Use -m to list or select the model. Those models can be absolute paths or model names when downloaded via r2ai, which uses the hugging face api
|
||||
|
||||
## Running r2ai-server
|
||||
|
||||
- Get usage: `r2pm -r r2ai-server`
|
||||
- List available servers: `r2pm -r r2ai-server -l`
|
||||
- List available models: `r2pm -r r2ai-server -m`
|
||||
|
||||
On Linux, models are stored in `~/.r2ai.models/`. File `~/.r2ai.model` lists the default model and other models.
|
||||
|
||||
**Example launching a local Mistral AI server:**
|
||||
|
||||
```
|
||||
$ r2pm -r r2ai-server -l r2ai -m mistral-7b-instruct-v0.2.Q2_K
|
||||
[12/13/24 10:35:22] INFO r2ai.server - INFO - [R2AI] Serving at port 8080 web.py:336
|
||||
```
|
||||
|
||||
File renamed without changes.
@@ -1 +0,0 @@
|
||||
recursive-include r2ai/ui *.tcss
|
||||
@@ -1,93 +1,9 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
PV=3.12
|
||||
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
|
||||
PYTHON?=python${PV}
|
||||
else
|
||||
PYTHON?=python3
|
||||
endif
|
||||
PIP=$(PYTHON) -m pip
|
||||
TARGETS=all clean mrproper user-install install user-uninstall uninstall fmt indent
|
||||
|
||||
LINTED=r2ai/code_block.py
|
||||
LINTED+=r2ai/bubble.py
|
||||
LINTED+=r2ai/const.py
|
||||
LINTED+=r2ai/backend/kobaldcpp.py
|
||||
# LINTED+=r2ai/index.py
|
||||
# LINTED+=r2ai/voice.py
|
||||
# LINTED+=r2ai/anthropic.py
|
||||
$(TARGETS):
|
||||
$(MAKE) -C src $@
|
||||
|
||||
ifeq ($(R2PM_BINDIR),)
|
||||
FATAL ERROR
|
||||
endif
|
||||
test: all
|
||||
$(MAKE) -C test
|
||||
|
||||
.PHONY: all all.old deps clean deps-global pub lint cilint
|
||||
.PHONY: install uninstall user-install user-uninstall
|
||||
|
||||
all: venv
|
||||
@./r2ai.sh
|
||||
|
||||
large:
|
||||
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
|
||||
|
||||
all.old:
|
||||
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
|
||||
$(PYTHON) main.py || $(MAKE) deps
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv venv
|
||||
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
|
||||
|
||||
deps: venv
|
||||
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
|
||||
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
pip install --force-reinstall -U --no-cache-dir .
|
||||
|
||||
clean:
|
||||
rm -rf venv
|
||||
rm -rf build
|
||||
find . -name "*.egg-info" -exec rm -rf {} +
|
||||
|
||||
mrproper:
|
||||
$(MAKE) clean
|
||||
|
||||
deps-global:
|
||||
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
|
||||
|
||||
user-install:
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
|
||||
|
||||
install: user-install
|
||||
-mkdir -p /usr/local/share/man/man1/r2ai.1
|
||||
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
install-decai:
|
||||
$(MAKE) -C decai user-install
|
||||
|
||||
install-server:
|
||||
$(MAKE) -C r2ai-server user-install
|
||||
|
||||
install-plugin user-install-plugin:
|
||||
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
-rm -f /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
user-uninstall-plugin uninstall-plugin:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
pub:
|
||||
$(PYTHON) -m build
|
||||
twine check dist/*
|
||||
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
|
||||
|
||||
cilint:
|
||||
pylint $(LINTED)
|
||||
|
||||
lint:
|
||||
pylint *.py r2ai/*.py
|
||||
.PHONY: $(TARGETS) test
|
||||
@@ -1,137 +1,115 @@
|
||||
```
|
||||
,______ .______ .______ ,___
|
||||
: __ \ \____ |: \ : __|
|
||||
| \____|/ ____|| _,_ || : |
|
||||
| : \ \ . || : || |
|
||||
| |___\ \__:__||___| || |
|
||||
|___| : |___||___|
|
||||
*
|
||||
# R2AI - Augmented reversing with LLM for radare2
|
||||
|
||||
```console
|
||||
╭─────────────────────────────────╮
|
||||
│ ,______ .______ .______ ,___ │
|
||||
╭──╮ │ : __ \ \____ |: \ : __| │
|
||||
│ _│_ │ | \____|/ ____|| _,_ || : | │
|
||||
│ O O < | : \ \ . || : || | │
|
||||
│ │╷ │ | |___\ \__:__||___| || | │
|
||||
│ ││ │ |___| : |___||___| │
|
||||
│ ─╯│ ╰─────────────────────────────────╯
|
||||
╰───╯
|
||||
```
|
||||
|
||||
[](https://github.com/radareorg/r2ai/actions/workflows/ci.yml)
|
||||
|
||||
Run a language model in local, without internet, to entertain you or help answering questions about radare2 or reverse engineering in general. Note that models used by r2ai are pulled from external sources which may behave different or respond unrealible information. That's why there's an ongoing effort into improving the post-finetuning using memgpt-like techniques which can't get better without your help!
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/r2clippy.jpg">
|
||||
</p>
|
||||
[](https://github.com/radareorg/radare2)
|
||||
|
||||
## Components
|
||||
|
||||
R2AI is structured into four independent components:
|
||||
This repository contains two plugins for radare2:
|
||||
|
||||
* r2ai (wip r2ai-native rewrite in C)
|
||||
* r2-like repl using r2pipe to comunicate with r2
|
||||
* supports auto solving mode
|
||||
* client and server openapi protocol
|
||||
* download and manage models from huggingface
|
||||
* decai
|
||||
* lightweight r2js plugin
|
||||
* focus on decompilation
|
||||
* talks to r2ai, r2ai-server, openai, anthropic or ollama
|
||||
* r2ai-plugin
|
||||
* requires r2lang-python
|
||||
* adds r2ai command inside r2
|
||||
* not recommended because of python versions pain
|
||||
* r2ai-server
|
||||
* list and select models downloaded from r2ai
|
||||
* simple cli tool to start local openapi webservers
|
||||
* supports llamafile, llamacpp, r2ai-w and kobaldcpp
|
||||
* **r2ai** - native [AI plugin](./src/README.md) for radare2
|
||||
* **decai** - r2js plugin with special [focus on decompilation](./decai/README.md)
|
||||
|
||||
If you are looking to use radare2 with other agents via MCP:
|
||||
|
||||
* **r2mcp** - the [official radare2 mcp](https://github.com/radare2/radare2-mcp)
|
||||
* **r2copilot** - the mcp with focus on CTF [r2copilot](https://github.com/darallium/r2-copilot)
|
||||
|
||||
If you are looking for a radare2 focused autonomous agent:
|
||||
* **r2agent** - automate radare2 workflows through autonomous agents. [r2agent](https://github.com/nitanmarcel/r2agent)
|
||||
|
||||
## Features
|
||||
|
||||
* Support Auto mode (see below) to solve tasks using function calling
|
||||
* Use local and remote language models (llama, ollama, openai, anthropic, ..)
|
||||
* Support OpenAI, Anthropic, Bedrock
|
||||
* Index large codebases or markdown books using a vector database
|
||||
* Slurp file and perform actions on that
|
||||
* Embed the output of an r2 command and resolve questions on the given data
|
||||
* Define different system-level assistant role
|
||||
* Set environment variables to provide context to the language model
|
||||
* Configure different roles and customize prompts
|
||||
* Scriptable via r2pipe via the r2ai command
|
||||
* Live with repl and batch mode from cli or r2 prompt
|
||||
* Accessible as an r2lang-python plugin, keeps session state inside radare2
|
||||
* Scriptable from python, bash, r2pipe, and javascript (r2papi)
|
||||
* Use different models, dynamically adjust query template
|
||||
* Load multiple models and make them talk between them
|
||||
* Support Automatic (ReAct) mode to solve tasks using function calling
|
||||
* Use local and remote language models (ollama, openai, grok, anthropic, ..)
|
||||
* RAG markdown, code or textfiles using its native vector database
|
||||
* Embed the output of an r2 command and resolve questions on the given data
|
||||
|
||||
## User defined Prompts
|
||||
|
||||
```console
|
||||
[0x00000000]> r2ai -q
|
||||
explain: Explain the current function -
|
||||
devices: Find and explain devices used -
|
||||
libs: Group imports by Libraries -
|
||||
varnames: Better variable names -
|
||||
autoname: Automatically suggest a better name for this function -
|
||||
vulns: Find vulnerabilities or bugs in the current function -
|
||||
signature: Suggest an improved function signature -
|
||||
dlopen: List libraries loaded with dlopen - Some libraries are loaded
|
||||
decompile: Augmented decompilation based on LLM -
|
||||
[0x00000000]>
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Running `make` will setup a python virtual environment in the current directory installing all the necessary dependencies and will get into a shell to run r2ai.
|
||||
The recommended way to install any of the r2ai components is via r2pm:
|
||||
|
||||
The installation is now splitted into two different targets:
|
||||
|
||||
* `make install` will place a symlink in `$BINDIR/r2ai`
|
||||
* `make install-plugin` will install the native r2 plugin into your home
|
||||
* `make install-decai` will install the decai r2js decompiler plugin
|
||||
* `make install-server` will install the decai r2js decompiler plugin
|
||||
|
||||
## Running
|
||||
|
||||
When installed via r2pm you can execute it like this:
|
||||
|
||||
```bash
|
||||
r2pm -r r2ai
|
||||
```console
|
||||
$ r2pm -Uci r2ai
|
||||
$ r2pm -Uci decai
|
||||
```
|
||||
|
||||
Additionally you can get the `r2ai` command inside r2 to run as an rlang plugin by installing the bindings:
|
||||
## Using r2ai
|
||||
|
||||
```bash
|
||||
r2pm -i rlang-python
|
||||
r2pm -i r2ai-plugin
|
||||
- Adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai`
|
||||
- You can also run the wrapper in $PATH: `r2pm -r r2ai`
|
||||
|
||||
Drop your API keys in environment variables or use the configuration file:
|
||||
|
||||
```console
|
||||
$ export ANTHROPIC_API_KEY=sk-ant-api03-CENSORED
|
||||
$ export OPENAI_API_KEY=sk-proj-6rlSPS-zN1v...
|
||||
```
|
||||
|
||||
After this you should get the `r2ai` command inside the radare2 shell. Set the `R2_DEBUG=1` environment to see the reasons why the plugin is not loaded if it's not there.
|
||||
Or edit the api keys file `~/.config/r2ai/apikeys.txt` run:
|
||||
|
||||
## Windows
|
||||
|
||||
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python3 -m pip install .
|
||||
python3 main.py
|
||||
```console
|
||||
$ r2ai -K
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Saving settings
|
||||
|
||||
There are 4 different ways to run `r2ai`:
|
||||
You may customize and save your configuration settings using your OS's default settings file (e.g `~/.radare2rc` on Linux).
|
||||
For example, the following configuration sets Claude 3.7 by default, with max output tokens to 64000.
|
||||
|
||||
* Standalone and interactive: `r2pm -r r2ai` or `python -m r2ai.cli`
|
||||
* Batch mode: `r2ai -c '-r act as a calculator' -c '3+3=?'`
|
||||
* As an r2 plugin: `r2 -i r2ai/plugin.py /bin/ls`
|
||||
* From radare2 (requires `r2pm -ci rlang-python r2ai-plugin`): `r2 -c 'r2ai -h'`
|
||||
* Using r2pipe: `#!pipe python -m r2ai.cli`
|
||||
* Define a macro command: `'$r2ai=#!pipe python -m r2ai.cli`
|
||||
|
||||
## Auto mode
|
||||
|
||||
When using OpenAI, Claude or any of the Functionary local models you can use the auto mode which permits the language model to execute r2 commands, analyze the output in loop and in a loop until it is resolved. Here's a sample session to achieve that:
|
||||
|
||||
* Video https://infosec.exchange/@radareorg/111946255058894583
|
||||
|
||||
```bash
|
||||
$ r2pm -i r2ai-plugin
|
||||
(env)$ r2 /bin/ls
|
||||
[0x00000000]> r2ai -m openai:gpt-4
|
||||
[0x00000000]> r2ai ' list the imports for this program
|
||||
[0x00000000]> r2ai ' draw me a donut
|
||||
[0x00000000]> r2ai ' decompile current function and explain it
|
||||
```console
|
||||
$ r2ai -E
|
||||
```
|
||||
|
||||
## Examples
|
||||
then you can type the commands you want to run when the r2ai plugin is loaded:
|
||||
|
||||
You can interact with r2ai from standalone python, from r2pipe via r2 keeping a global state or using the javascript interpreter embedded inside `radare2`.
|
||||
```console
|
||||
r2ai -e api=anthropic
|
||||
r2ai -e model=claude-sonnet-5-5
|
||||
r2ai -e max_tokens=64000
|
||||
```
|
||||
|
||||
* [conversation.r2.js](examples/conversation.r2.js) - load two models and make them talk to each other
|
||||
## Further Reading
|
||||
|
||||
### Development/Testing
|
||||
* There's [a chapter](https://book.rada.re/plugins/r2ai.html) in the official r2book
|
||||
* Cryptax on [lmstudio+gptoss](https://cryptax.medium.com/r2ai-with-lmstudio-and-gpt-oss-08efa5ea2476) blog post
|
||||
* Malware analysis [with r2ai](https://arxiv.org/pdf/2504.07574) by Cryptax and Daniel Nakov
|
||||
* Analysis of [Linux/Trigona ransomware](https://cryptax.medium.com/linux-trigona-analysis-with-r2ai-3e2bd1815e52), [Linux/Prometei botnet](https://cryptax.medium.com/reversing-a-prometei-botnet-binary-with-r2-and-ai-part-one-3cdb3dc6ffab) and [W32/SkyAI](https://cryptax.medium.com/w32-skyai-uses-ai-so-do-i-d33f04d63534with) with r2ai
|
||||
|
||||
Just run `make` .. or well `python3 -m r2ai.cli`
|
||||
## Videos
|
||||
|
||||
### TODO
|
||||
|
||||
* add "undo" command to drop the last message
|
||||
* dump / restore conversational states (see -L command)
|
||||
* Implement `~`, `|` and `>` and other r2shell features
|
||||
- [Solving a crackme](https://infosec.exchange/@radareorg/111946255058894583)
|
||||
- [De-obfuscation of malware Linux/Ladvix](https://asciinema.org/a/724126)
|
||||
- [Analysis of the /fast option inside Linux/Trigona ransomware](https://asciinema.org/a/pBPEaJhp6cunWSKFpBUDTgPt4)
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
vim decai/src/constants.ts
|
||||
make -C decai/src
|
||||
vim src/r2ai.h
|
||||
vim src/meson.build
|
||||
vim VERSION
|
||||
@@ -0,0 +1,13 @@
|
||||
# Decompiler based AI
|
||||
|
||||
## Actions
|
||||
|
||||
* Build: `make -C src`
|
||||
* Install: `make user-install`
|
||||
* Test: `r2 -A -qc "decai -e api=ollamacloud;decai -e model=glm-5;decai -d @ 0x100000924" /bin/ls`
|
||||
|
||||
## Hints
|
||||
|
||||
* Never read the minified, autogenerated `decai.r2.js`
|
||||
* Focus and work only on the TypeScript source
|
||||
* Never run `tsc` or `npx`. Always use `make` targets to compile
|
||||
+10
-3
@@ -3,10 +3,17 @@ SPD=$(shell r2 -H R2_LIBR_PLUGINS)
|
||||
BF=/bin/dd
|
||||
|
||||
all:
|
||||
@echo 'You can now use 'decai' command'
|
||||
r2 -i decai.r2.js $(BF)
|
||||
@echo "Run: 'make user-install' to install it in your home"
|
||||
@echo "Run: 'make -C src' to recompile the r2.js"
|
||||
|
||||
test:
|
||||
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
|
||||
|
||||
fmt indent:
|
||||
$(MAKE) -C src fmt
|
||||
|
||||
user-install:
|
||||
mkdir -p "$(PD)"
|
||||
cp decai.r2.js $(PD)/decai.r2.js
|
||||
|
||||
user-uninstall:
|
||||
@@ -19,4 +26,4 @@ uninstall:
|
||||
make user-uninstall PD=$(SPD)
|
||||
|
||||
vs open:
|
||||
open -a "Visual Studio Code" .
|
||||
open -a "Visual Studio Code" src
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
# Decai
|
||||
|
||||
The AI based Decompiler plugin for radare2
|
||||
|
||||
- Written in plain Javascript
|
||||
- No dependencies than radare2 and curl in PATH
|
||||
- Uses local ollama by default, but also r2ai-server
|
||||
- Use services like Anthropic, OpenAI, HF, XAI, DeepSeek, ..
|
||||
|
||||
Features
|
||||
|
||||
- Auto mode with function calling with ANY model
|
||||
- Uses the r2 pseudo decompiler by default, supports any other
|
||||
- Explain purpose and auto-document functions
|
||||
- Recursive decompilation to inline stubs
|
||||
- Autoname functions and perform type propagation
|
||||
- Find vulnerabilities, guide you and write exploits
|
||||
- Choose any natural language (not just English)
|
||||
- Choose output programming language (not just C)
|
||||
- Chain queries to manually fine tune the results
|
||||
- Customize decompilation prompt at any time
|
||||
|
||||
## Installation
|
||||
|
||||
Using r2pm: `r2pm -ci decai`
|
||||
|
||||
From source: `make user-install`
|
||||
|
||||
## Setup
|
||||
|
||||
```console
|
||||
[0x00000000]> decai
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
decai -e - display and change eval config vars
|
||||
decai -E - edit decai.txt
|
||||
decai -h - show this help
|
||||
decai -i [f] [q] - include given file and query
|
||||
decai -n - suggest better function name
|
||||
decai -q [text] - query language model with given text
|
||||
decai -Q [text] - query on top of the last output
|
||||
decai -r - change role prompt (same as: decai -e prompt)
|
||||
decai -R - reset role prompt to default prompt
|
||||
decai -s - function signature
|
||||
decai -v - show local variables
|
||||
decai -V - find vulnerabilities
|
||||
decai -x - eXplain current function
|
||||
```
|
||||
|
||||
Configuration options:
|
||||
|
||||
```console
|
||||
[0x00000000]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e apitype=chat
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
|
||||
decai -e ctxfile=
|
||||
decai -e headers=
|
||||
decai -e cmds=pdc
|
||||
decai -e cache=false
|
||||
decai -e lang=C
|
||||
decai -e hlang=English
|
||||
decai -e debug=false
|
||||
decai -e model=
|
||||
decai -e maxinputtokens=-1
|
||||
```
|
||||
|
||||
## Running decai
|
||||
|
||||
Decai is used from `r2` (e.g `r2 ./mybinary`). Get help with `decai -h`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -h
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -a [query] - solve query with auto mode
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
...
|
||||
```
|
||||
|
||||
List configuration variables with `decai -e`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
|
||||
decai -e ctxfile=
|
||||
...
|
||||
```
|
||||
|
||||
Persist user defaults in `~/.config/r2ai/decai.txt` with one `key=value` entry
|
||||
per line, then edit it with `decai -E`. That file is loaded on the first
|
||||
`decai` command in a session and reloaded after `decai -E` exits.
|
||||
|
||||
Use `decai -e http=curl` to force system curl, `http=r2` to require `r2.http()`,
|
||||
or `http=auto` (the default) to use `r2.http()` when available and curl otherwise.
|
||||
Failed `r2.http()` calls return an error without falling back to curl. The
|
||||
`timeout` setting applies only to system curl. Save `http=curl` in
|
||||
`~/.config/r2ai/decai.txt` to persist the override.
|
||||
|
||||
List possible APIs to discuss with AI: `decai -e api=?`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=?
|
||||
r2ai
|
||||
claude
|
||||
openapi
|
||||
...
|
||||
```
|
||||
|
||||
Extra headers can be injected globally with `decai -e headers=...` or from the
|
||||
environment with `DECAI_HEADERS` / `R2AI_HEADERS`. Use one header per line, for
|
||||
example:
|
||||
|
||||
```console
|
||||
[0x00406cac]> decai -e headers=Authorization: Bearer sk-123\nUser-Agent: curl/8.7.1
|
||||
```
|
||||
|
||||
### Example using a local model and ollama
|
||||
|
||||
For example, if Ollama serves model codegeex4:latest (`ollama ls`), set decai
|
||||
API as `ollama` and model `codegeex4:latest`.
|
||||
|
||||
```
|
||||
[0x00002d30]> decai -e api=ollama
|
||||
[0x00002d30]> decai -e model=codegeex4:latest
|
||||
[0x00002d30]> decai -q Explain what forkpty does in 2 lines
|
||||
The `forkpty` function creates a new process with a pseudo-terminal, allowing the parent to interact with the child via standard input/output/err and controlling its terminal.
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See
|
||||
[https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
|
||||
|
||||
```c
|
||||
$ cat stack-overflow/bug.c
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char buf[32];
|
||||
strcpy (buf, argv[1]);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
$ r2 buffer-overflow/a.out
|
||||
[0x100003f58]> decai -d
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char buffer[32];
|
||||
int result = 0;
|
||||
|
||||
if (argc > 1 && argv[1] != NULL) {
|
||||
strcpy(buffer, argv[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Example using a local Mistral model and r2ai-server
|
||||
|
||||
For example, assuming we have a _local_ Mistral AI server running on port 8080
|
||||
with `r2ai-server`, we can decompile a given function with `decai -d`. The
|
||||
server shows it received the question:
|
||||
|
||||
```
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -R
|
||||
127.0.0.1 - - [13/Dec/2024 10:40:49] "GET /cmd/-R HTTP/1.1" 200 -
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -i /tmp/.pdc.txt Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change goto into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like string:. Transform this pseudocode into C
|
||||
```
|
||||
|
||||
### Example using a Mistral API key
|
||||
|
||||
Put the API key in `~/.r2ai.mistral-key`.
|
||||
|
||||
````
|
||||
[0x000010d0]> decai -e api=mistral
|
||||
[0x000010d0]> decai -d main
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char password[40];
|
||||
char input[40];
|
||||
...
|
||||
````
|
||||
|
||||
### Example with ChatGPT 4
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=openai
|
||||
[0x00406cac]> decai -d
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void daemonize() {
|
||||
daemon(1, 0);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## WASM HTTP
|
||||
|
||||
With radare2's WASI HTTP build, decai defaults to `r2.http()` to reach the existing
|
||||
`r_socket_http_get/post` C APIs. Headers and JSON payloads go directly to the
|
||||
host HTTP adapter; no shell curl, temporary payload file or WebSocket proxy is
|
||||
needed. A failed/denied HTTP call is returned as an error, without a shell
|
||||
fallback. In `http=auto` mode, runtimes without `r2.http()` use system curl.
|
||||
|
||||
Enable HTTP in the embedding application (`enabled: true` in JavaScript or
|
||||
`network=True` in Python). The radare2 sandbox must permit both `socket` and
|
||||
`network`; process execution is not needed. Browser requests remain subject
|
||||
to CORS. The host adapter controls the HTTP deadline in WASM; decai's `timeout`
|
||||
setting still controls native curl only. Responses are buffered, not streamed.
|
||||
|
||||
This requires radareorg/radare2#27037 and the rebuilt decai script. For the
|
||||
C r2ai plugin compiled into WASM, the `auto` HTTP backend likewise selects
|
||||
radare2's C HTTP implementation; native backend selection is unchanged.
|
||||
+140
-508
@@ -1,539 +1,171 @@
|
||||
(function () {
|
||||
const decaiHelp = `
|
||||
# Using Decai
|
||||
📦
|
||||
27680 /main.js
|
||||
✄
|
||||
var te="1.5.4",b="decai",w="~/.config/r2ai",N=w+"/apikeys.txt",U=w+"/decai.txt",T="Rewrite this pseudocode into concise and clean code. Output only the provided function. Do not add wrappers, helper examples, test code, or main-like functions. Replace goto with structured control flow, simplify as much as possible, infer types and use better names for variables and parameters, some strings may be appearing as comments, preserve only what is implied by the input, and remove dead code.",L={decai:`# Using Decai
|
||||
|
||||
You must run an r2ai-server in local or connect to a remote backend via api:
|
||||
Decai is the radare2 plugin for decompiling functions with the help of language models.
|
||||
|
||||
## Local backends:
|
||||
By default uses a local ollama server, but can you can pick any other service by using 'decai -e api=?'.
|
||||
|
||||
### R2AI
|
||||
[0x00000000]> decai -e api=?
|
||||
r2ai deepseek anthropic claude gemini groq hf lmstudio mistral ollama ollamacloud openapi openai openrouter vllm xai zen
|
||||
|
||||
Run 'decai -e api=r2ai' inside r2. Optimized for 'r2ai -w' as backend (see below)
|
||||
## Using Ollama
|
||||
|
||||
### OpenAPI
|
||||
* Visit https://ollama.com to install it.
|
||||
* Download the model of choice: 'ollama run llama3.3'
|
||||
* Configure decai to use the given model with: 'decai -e model=?'
|
||||
|
||||
You can use ollama, llamacpp, r2ai-server, etc
|
||||
These are the most recommended models for decompiling in local:
|
||||
|
||||
It connects to decai -e host/port via OpenAPI rest endpoints.
|
||||
* hhao/qwen2.5-coder-tools:latest (18GB of ram)
|
||||
* hhao/qwen2.5-coder-tools:32b (24GB of ram required)
|
||||
|
||||
### Setting up r2ai-server:
|
||||
## Common Options
|
||||
* 'decai -e baseurl=<url>' override default host and port for API endpoint (e.g., 'http://localhost:11434')
|
||||
* 'decai -e apitype=chat|generate' choose the Ollama API endpoint (default: chat)
|
||||
* 'decai -e headers=Authorization: Bearer ...\\nUser-Agent: curl/8.7.1' add or override HTTP headers
|
||||
* "export DECAI_HEADERS='Authorization: Bearer ...\\nUser-Agent: curl/8.7.1'" set extra headers from the environment
|
||||
* 'decai -e http=auto|r2|curl' select the HTTP transport (default: auto, prefers r2.http)
|
||||
* 'decai -e timeout=0' disable curl timeouts, or set a positive number of seconds
|
||||
|
||||
* 'decai -e deterministic=true' to remove randomness from decompilation responses
|
||||
* 'decai -e lang=Python' to output the decompilation in Python instead of C
|
||||
* 'decai -e hlang=Catalan' to add comments or explanations in that language (instead of English)
|
||||
* 'decai -e cmds=pdd,pdg' use r2dec and r2ghidra instead of r2's pdc as input for decompiling
|
||||
* 'decai -e prompt=..' default prompt must be fine for most models and binaries, feel free to tweak it
|
||||
|
||||
## API Keys
|
||||
|
||||
Remove services like OpenAI, Mistral, Anthropic, Grok, Gemini, .. require API keys to work.
|
||||
|
||||
See 'decai -k' to list the status of available APIkeys
|
||||
Use 'decai -K' to edit apikeys.txt and 'decai -E' to edit decai.txt.
|
||||
|
||||
Decai will pick them from the environment or the config files in your home:
|
||||
|
||||
* echo KEY > ~/.r2ai.openai-key
|
||||
* export OPENAI_API_KEY=...
|
||||
* put one 'key=value' pair per line in ~/.config/r2ai/decai.txt
|
||||
|
||||
## Using the R2AI Server:
|
||||
|
||||
Install r2ai or r2ai-server with r2pm:
|
||||
|
||||
r2pm -ci r2ai
|
||||
[0x0000000]> decai -e api=r2ai
|
||||
[0x0000000]> r2pm -ci r2ai
|
||||
|
||||
Choose one of the recommended models (after r2pm -r r2ai):
|
||||
|
||||
* -m ibm-granite/granite-20b-code-instruct-8k-GGUF
|
||||
* -m QuantFactory/granite-8b-code-instruct-4k-GGUF
|
||||
* -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
* -m ibm-granite/granite-20b-code-instruct-8k-GGUF
|
||||
* -m QuantFactory/granite-8b-code-instruct-4k-GGUF
|
||||
* -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
|
||||
Start the webserver:
|
||||
|
||||
$ r2pm -r r2ai
|
||||
$ echo $CLAUDEAPIKEY > ~/.r2ai.anthropic
|
||||
[r2ai:0x0000000]> -m anthropic:claude-3-5-sonnet-20241022
|
||||
[r2ai:0x0000000]> -w
|
||||
Webserver listening at port 8080
|
||||
$ r2pm -r r2ai-server -l r2ai -m granite-8...
|
||||
`,auto:`# Radare2 Auto Mode
|
||||
|
||||
You can also make r2ai -w talk to an 'r2ai-server' using this line:
|
||||
Use function calling to execute radare2 commands in order to resolve the user request defined in the "User Prompt" section, analyze the responses attached in the "Command Results" section.
|
||||
|
||||
[r2ai:0x0000000]> -m openapi:http://localhost:8080
|
||||
[r2ai:0x0000000]> -e http.port=8082
|
||||
## Function Calling
|
||||
|
||||
[0x0000000]> decai -e host=http://localhost:8082
|
||||
## Remote backends:
|
||||
Respond ONLY using plain JSON. Process user query and decide which function calls are necessary to solve the task.
|
||||
|
||||
Specify the service to use:
|
||||
* decai -e api=openai
|
||||
* decai -e api=claude
|
||||
* decai -e api=hf
|
||||
1. Analyze the user request to determine if we need to run commands to extend the knowledge and context of the problem.
|
||||
2. If function call is needed, construct the JSON like this:
|
||||
- Fill the "action" key with the "r2cmd" value.
|
||||
- Specify the "command" as a string.
|
||||
- Optionally, provide a "reason" and "description"
|
||||
3. If the answer can be provided and no more function calls are required:
|
||||
- Use the key "action": "reply".
|
||||
- Include "response" with the direct answer to the user query.
|
||||
|
||||
Write the API keys in corresponding files:
|
||||
Return the result as a JSON object.
|
||||
|
||||
* ~/.r2ai.openai-key
|
||||
* ~/.r2ai.huggingface-key
|
||||
* ~/.r2ai.anthropic-key
|
||||
### Sample Function Calling Communication
|
||||
|
||||
## Make those changes permanent
|
||||
Command Results: already performed actions with their responses
|
||||
User Prompt: "Count how many functions we have here."
|
||||
Response:
|
||||
{
|
||||
"action": "r2cmd",
|
||||
"command": "aflc",
|
||||
"description": "Count functions"
|
||||
"reason": "Evaluate if the program is analyzed before running aaa"
|
||||
}
|
||||
|
||||
You can write your custom decai commands in your ~/.radare2rc file.
|
||||
## Rules
|
||||
|
||||
`;
|
||||
const command = "decai";
|
||||
let decaiHost = "http://localhost";
|
||||
let decaiPort = "8080";
|
||||
let decaiApi = "r2"; // uses /cmd endpoint
|
||||
let decaiCommands = "pdc";
|
||||
let decaiLanguage = "C";
|
||||
let decaiHumanLanguage = "English";
|
||||
let decaiDebug = false;
|
||||
let decaiContextFile = "";
|
||||
let decaiModel = "";
|
||||
let lastOutput = "";
|
||||
let decaiCache = false;
|
||||
// let decprompt = "Do not explain, respond using ONLY code. Simplify and make it more readable. Use better variable names, keep it simple and avoid unnecessary logic, rewrite 'goto' into higher level constructs, Use comments like 'string:' to resolve function call arguments";
|
||||
let decprompt = "Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like 'string:'";
|
||||
Use radare2 to resolve user requests.
|
||||
|
||||
function decaiEval(arg) {
|
||||
const [k, v] = arg.split("=");
|
||||
if (!v) {
|
||||
switch (k) {
|
||||
case "model":
|
||||
console.log(decaiModel);
|
||||
break;
|
||||
case "debug":
|
||||
console.log(decaiDebug);
|
||||
break;
|
||||
case "api":
|
||||
console.log(decaiApi);
|
||||
break;
|
||||
case "hlang":
|
||||
console.log(decaiHumanLanguage);
|
||||
break;
|
||||
case "lang":
|
||||
console.log(decaiLanguage);
|
||||
break;
|
||||
case "cache":
|
||||
console.log(decaiCache);
|
||||
break;
|
||||
case "cmds":
|
||||
console.log(decaiCommands);
|
||||
break;
|
||||
case "prompt":
|
||||
console.log(decprompt);
|
||||
break;
|
||||
case "ctxfile":
|
||||
console.log(decaiContextFile);
|
||||
break;
|
||||
case "host":
|
||||
console.log(decaiHost);
|
||||
break;
|
||||
case "port":
|
||||
console.log(decaiPort);
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown key");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (k) {
|
||||
case "debug":
|
||||
decaiDebug = (v === "true" || v === "1");
|
||||
break;
|
||||
case "api":
|
||||
if (v === "?") {
|
||||
console.error("r2ai\nclaude\nopenapi\nopenai\nhf");
|
||||
} else {
|
||||
decaiApi = v;
|
||||
}
|
||||
break;
|
||||
case "model":
|
||||
decaiModel = v;
|
||||
break;
|
||||
case "cache":
|
||||
decaiCache = v === "true" || v == 1;
|
||||
break;
|
||||
case "ctxfile":
|
||||
decaiContextFile = v;
|
||||
break;
|
||||
case "hlang":
|
||||
decaiHumanLanguage = v;
|
||||
break;
|
||||
case "lang":
|
||||
decaiLanguage = v;
|
||||
break;
|
||||
case "cmds":
|
||||
decaiCommands = v;
|
||||
break;
|
||||
case "prompt":
|
||||
decprompt = v;
|
||||
break;
|
||||
case "host":
|
||||
decaiHost = v;
|
||||
break;
|
||||
case "port":
|
||||
decaiPort = v;
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
function usage() {
|
||||
console.error("Usage: " + command + " (-h) ...");
|
||||
console.error(" " + command + " -H - help setting up r2ai");
|
||||
console.error(" " + command + " -d [f1 ..] - decompile given functions");
|
||||
console.error(" " + command + " -dd [..] - same as above, but ignoring cache");
|
||||
console.error(" " + command + " -D [query] - decompile current function with given extra query");
|
||||
console.error(" " + command + " -e - display and change eval config vars");
|
||||
console.error(" " + command + " -h - show this help");
|
||||
console.error(" " + command + " -i [f] [q] - include given file and query");
|
||||
console.error(" " + command + " -n - suggest better function name");
|
||||
console.error(" " + command + " -q [text] - query language model with given text");
|
||||
console.error(" " + command + " -Q [text] - query on top of the last output");
|
||||
console.error(" " + command + " -r - change role prompt (same as: decai -e prompt)");
|
||||
console.error(" " + command + " -s - function signature");
|
||||
console.error(" " + command + " -v - show local variables");
|
||||
console.error(" " + command + " -V - find vulnerabilities");
|
||||
console.error(" " + command + " -x - eXplain current function");
|
||||
}
|
||||
function r2aiAnthropic(msg, hideprompt) {
|
||||
const claudeKey = r2.cmd("'cat ~/.r2ai.anthropic-key").trim()
|
||||
const claudeModel = (decaiModel.length > 0)? decaiModel: "claude-3-5-sonnet-20241022";
|
||||
if (claudeKey === '') {
|
||||
return "Cannot read ~/.r2ai.anthropic-key";
|
||||
}
|
||||
const payload = JSON.stringify({
|
||||
model: claudeModel,
|
||||
max_tokens: 5128,
|
||||
messages: [
|
||||
{
|
||||
"role": "user",
|
||||
"content": hideprompt ? msg
|
||||
: decprompt + ", Rewrite this pseudocode into " + decaiLanguage + "\n" + msg
|
||||
}
|
||||
]
|
||||
});
|
||||
const curlcmd = `curl -s https://api.anthropic.com/v1/messages
|
||||
-H "Content-Type: application/json"
|
||||
-H "anthropic-version: 2023-06-01"
|
||||
-H "x-api-key: ${claudeKey}"
|
||||
-d '${payload}'`.replace(/\n/g, "");
|
||||
if (decaiDebug) {
|
||||
console.error(curlcmd);
|
||||
}
|
||||
const res = r2.syscmds(curlcmd);
|
||||
if (decaiDebug) {
|
||||
console.error(res);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(res).content[0].text;
|
||||
} catch(e) {
|
||||
console.error("ERROR: " + e + "(" + res + ")");
|
||||
}
|
||||
return "error invalid response";
|
||||
}
|
||||
function r2aiHuggingFace(msg, hideprompt) {
|
||||
const hfKey = r2.cmd("'cat ~/.r2ai.huggingface-key").trim();
|
||||
if (hfKey === '') {
|
||||
return "ERROR: Cannot read ~/.r2ai.huggingface-key";
|
||||
}
|
||||
let hfModel = "deepseek-ai/DeepSeek-Coder-V2-Instruct";
|
||||
if (decaiModel.length > 0) {
|
||||
hfModel = decaiModel;
|
||||
}
|
||||
// const hfModel = "instructlab/granite-7b-lab"
|
||||
// const hfModel = "TheBloke/Llama-2-7B-GGML"
|
||||
// const hfModel = "meta-llama/Llama-3.1-8B-Instruct";
|
||||
// const hfModel = "meta-llama/Llama-3.2-1B-Instruct";
|
||||
// const hfModel = "Qwen/Qwen2.5-72B-Instruct";
|
||||
const query = hideprompt? msg: decprompt + ", Output in " + decaiLanguage + " language\n" + msg;
|
||||
const payload = JSON.stringify({
|
||||
inputs: query,
|
||||
parameters: {
|
||||
max_new_tokens: 5128
|
||||
}
|
||||
});
|
||||
const curlcmd = `curl -s https://api-inference.huggingface.co/models/${hfModel}
|
||||
-H "Authorization: Bearer ${hfKey}"
|
||||
-H "Content-Type: application/json"
|
||||
-d '${payload}'`.replace(/\n/g, "");
|
||||
//if (decaiDebug) {
|
||||
// console.log(curlcmd);
|
||||
//}
|
||||
* Explain each step in the "reason" field of the JSON.
|
||||
* Follow the initial analysis instructions.
|
||||
* Output only valid JSON as specified.
|
||||
* Decompile and inspect functions, starting from main.
|
||||
* Run only the needed commands to gather info.
|
||||
* Use "@ (address|symbol)" to seek temporarily.
|
||||
* Output should be a verbose markdown report.
|
||||
* Use "sym." or "fcn." prefixes if "pdc" is empty.
|
||||
* If a seek fails, use "f~name" to find the symbol's address.
|
||||
|
||||
const res = r2.syscmds(curlcmd);
|
||||
// Debug response instead of request
|
||||
if (decaiDebug) {
|
||||
console.log(res)
|
||||
}
|
||||
### Initial Analysis
|
||||
|
||||
try {
|
||||
const o = JSON.parse(res);
|
||||
if (o.error) {
|
||||
return "ERROR: " + o.error;
|
||||
}
|
||||
return JSON.parse(res).generated_text;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.log(res);
|
||||
}
|
||||
return "error invalid response";
|
||||
}
|
||||
1. Run "aflc" to count the number of functions
|
||||
2. If the output of "aflc" is "0" run "aaa" once, then "aflc" again
|
||||
3. Run only one command at a time (do not use ";")
|
||||
|
||||
function r2aiOpenAI(msg, hideprompt) {
|
||||
const openaiKey = r2.cmd("'cat ~/.r2ai.openai-key").trim()
|
||||
if (openaiKey === '') {
|
||||
return "Cannot read ~/.r2ai.openai-key";
|
||||
}
|
||||
const openaiModel = (decaiModel.length > 0)? decaiModel: "gpt-4";
|
||||
const query = hideprompt? msg: decprompt + ", Output in " + decaiLanguage + " language\n" + msg;
|
||||
const payload = JSON.stringify({
|
||||
model: openaiModel,
|
||||
max_tokens: 5128,
|
||||
messages: [
|
||||
// { "role": "system", "content": hideprompt? decprompt: "" },
|
||||
{ "role": "user", "content": query }
|
||||
]
|
||||
});
|
||||
const curlcmd = `curl -s https://api.openai.com/v1/chat/completions
|
||||
-H "Content-Type: application/json"
|
||||
-H "Authorization: Bearer ${openaiKey}"
|
||||
-d '${payload}' #`.replace(/\n/g, "");
|
||||
if (decaiDebug) {
|
||||
console.log(curlcmd);
|
||||
}
|
||||
const res = r2.syscmds(curlcmd);
|
||||
try {
|
||||
return JSON.parse(res).choices[0].message.content;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
console.log(res);
|
||||
}
|
||||
return "error invalid response";
|
||||
}
|
||||
function r2aiOpenAPI(msg, hideprompt) {
|
||||
const query = hideprompt? msg: decprompt + ", Transform this pseudocode into " + decaiLanguage + "\n" + msg;
|
||||
const payload = JSON.stringify({ "prompt": query });
|
||||
const curlcmd = `curl -s ${decaiHost}:${decaiPort}/completion
|
||||
-H "Content-Type: application/json"
|
||||
-d '${payload}' #`.replace(/\n/g, "");
|
||||
if (decaiDebug) {
|
||||
console.log(curlcmd);
|
||||
}
|
||||
const res = r2.syscmds(curlcmd);
|
||||
try {
|
||||
return JSON.parse(res).content;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
console.log(res);
|
||||
}
|
||||
return "error invalid response";
|
||||
}
|
||||
function decaiDecompile(args, extraQuery, useCache) {
|
||||
if (useCache) {
|
||||
const cachedAnotation = r2.cmd("anos").trim();
|
||||
if (cachedAnotation.length > 0) {
|
||||
return cachedAnotation;
|
||||
}
|
||||
}
|
||||
let out = "";
|
||||
const appendQuery = extraQuery? " " + args: "";
|
||||
const origColor = r2.cmd("e scr.color");
|
||||
try {
|
||||
args = args.slice(2).trim();
|
||||
const file = "/tmp/.pdc.txt";
|
||||
r2.call("rm .pdc.txt");
|
||||
r2.call("rm " + file);
|
||||
r2.cmd("echo > " + file);
|
||||
let count = 0;
|
||||
let text = "";
|
||||
if (decaiContextFile !== "") {
|
||||
if (r2.cmd2("test -f " + decaiContextFile).value === 0) {
|
||||
text += "Context:\n";
|
||||
text += "[RULES]\n";
|
||||
text += r2.cmd("cat " + decaiContextFile);
|
||||
text += "[/RULES]\n";
|
||||
}
|
||||
}
|
||||
r2.cmd("e scr.color=0");
|
||||
for (const c of decaiCommands.split(",")) {
|
||||
if (c.trim() === "") {
|
||||
continue;
|
||||
}
|
||||
const oneliner = (extraQuery || args.trim().length === 0)? c : c + "@@= " + args;
|
||||
const output = r2.cmd(oneliner);
|
||||
if (output.length > 5) {
|
||||
text += "Output from " + c + ":\n";
|
||||
text += "[BEGIN]\n";
|
||||
text += output + "\n";
|
||||
text += "[END]\n";
|
||||
count++;
|
||||
}
|
||||
}
|
||||
r2.cmd("e scr.color=" + origColor);
|
||||
if (count === 0) {
|
||||
console.error("Nothing to do.");
|
||||
return;
|
||||
}
|
||||
r2ai("-R");
|
||||
const query = (decprompt + appendQuery).trim() + ". Transform this pseudocode into " + decaiLanguage;
|
||||
out = r2ai(query, text);
|
||||
lastOutput = out;
|
||||
} catch (e) {
|
||||
r2.cmd("e scr.color=" + origColor);
|
||||
console.error(e, e.stack);
|
||||
}
|
||||
if (useCache && out.length > 1) {
|
||||
r2.call("ano=base64:" + b64(out));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function fileDump(fileName, fileData) {
|
||||
const d = b64(fileData);
|
||||
r2.cmd("p6ds " + d + " > " + fileName);
|
||||
}
|
||||
function r2ai(queryText, fileData, hideprompt) {
|
||||
if (!fileData) {
|
||||
fileData = "";
|
||||
}
|
||||
fileData = fileData.replace(/\`/g, '').replace(/'/g, '"');
|
||||
queryText = queryText.replace(/'/g, '');
|
||||
if (decaiApi === "r2" || decaiApi === "r2ai") {
|
||||
const fileName = "/tmp/.pdc.txt";
|
||||
fileDump(fileName, fileData);
|
||||
const q = queryText.startsWith("-")? queryText: ["-i", fileName, queryText].join(" ");
|
||||
const host = decaiHost + ":" + decaiPort + "/cmd"; // "http://localhost:8080/cmd";
|
||||
const ss = q.replace(/ /g, "%20").replace(/'/g, "\\'");
|
||||
const cmd = 'curl -s "' + host + '/' + ss + '" || echo "Cannot curl, use r2ai-server or r2ai -w"';
|
||||
if (decaiDebug) {
|
||||
console.error(cmd);
|
||||
}
|
||||
return r2.syscmds(cmd);
|
||||
// return r2.cmd(cmd);
|
||||
}
|
||||
if (fileData === "" || queryText.startsWith("-")) { // -i
|
||||
return "";
|
||||
}
|
||||
const q = queryText + ":\n" + fileData;
|
||||
if (decaiApi === "anthropic" || decaiApi === "claude") {
|
||||
return r2aiAnthropic(q, hideprompt);
|
||||
}
|
||||
if (decaiApi === "huggingface" || decaiApi === "hf") {
|
||||
return r2aiHuggingFace(q, hideprompt);
|
||||
}
|
||||
if (decaiApi === "openapi") {
|
||||
return r2aiOpenAPI(q, hideprompt);
|
||||
}
|
||||
if (decaiApi === "openai") {
|
||||
return r2aiOpenAI(q, hideprompt);
|
||||
}
|
||||
return "Unknown value for 'decai -e api'. Use r2ai, claude, hf, openapi or openai";
|
||||
}
|
||||
function r2aidec(args) {
|
||||
if (args === "") {
|
||||
usage();
|
||||
} else if (args[0] === "-") {
|
||||
let out = "";
|
||||
switch (args[1]) {
|
||||
case "H": // "-H"
|
||||
console.log(decaiHelp);
|
||||
break;
|
||||
case "n": // "-n"
|
||||
case "f": // "-f"
|
||||
out = r2.cmd("pdc");
|
||||
var considerations = r2.cmd("fd.").trim().split(/\n/).filter(x=>!x.startsWith("secti")).join(",");
|
||||
// console.log(considerations);
|
||||
r2ai("-R");
|
||||
out = r2ai("give me a better name for this function. the output must be: 'afn NEWNAME'. do not include the function code, only the afn line. consider: " + considerations, out).trim();
|
||||
out += " @ " + r2.cmd("?v $FB").trim();
|
||||
break;
|
||||
case "v": // "-v"
|
||||
out = r2.cmd("afv;pdc");
|
||||
r2ai("-R");
|
||||
out = r2ai("guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands", out);
|
||||
break;
|
||||
case "i": // "-i"
|
||||
args = args.slice(2).trim().split(/ /g, 2);
|
||||
if (args.length === 2) {
|
||||
const file = args[0];
|
||||
const query = args[1];
|
||||
const fileData = r2.cmd("cat " + file);
|
||||
console.log(r2ai(query, fileData, true));
|
||||
} else {
|
||||
console.log("Use: decai -i [file] [query ...]");
|
||||
}
|
||||
break;
|
||||
case "r": // "-r"
|
||||
args = args.slice(2).trim();
|
||||
if (args) {
|
||||
decprompt = args
|
||||
} else {
|
||||
console.log(decprompt);
|
||||
}
|
||||
break;
|
||||
case "s": // "-s"
|
||||
out = r2.cmd("afv;pdc");
|
||||
out = "'afs " + r2ai("analyze the uses of the arguments and return valu to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body, ONLY output the function signature, like if it was going to be used in a C header", out);
|
||||
let brace = out.indexOf("{");
|
||||
if (brace !== -1) {
|
||||
out = out.substring(0, brace);
|
||||
}
|
||||
### Special cases
|
||||
|
||||
break;
|
||||
case "V": // "-V"
|
||||
r2aidec("-Q find vulnerabilities, dont show the code, only show the response, provide a sample exploit
|
||||
break;
|
||||
case "e": // "-e"
|
||||
args = args.slice(2).trim();
|
||||
if (args) {
|
||||
decaiEval(args);
|
||||
} else {
|
||||
console.log("decai -e api=" + decaiApi);
|
||||
console.log("decai -e host=" + decaiHost);
|
||||
console.log("decai -e port=" + decaiPort);
|
||||
console.log("decai -e prompt=" + decprompt);
|
||||
console.log("decai -e ctxfile=" + decaiContextFile);
|
||||
console.log("decai -e cmds=" + decaiCommands);
|
||||
console.log("decai -e cache=" + decaiCache);
|
||||
console.log("decai -e lang=" + decaiLanguage);
|
||||
console.log("decai -e hlang=" + decaiHumanLanguage);
|
||||
console.log("decai -e debug=" + decaiDebug);
|
||||
console.log("decai -e model=" + decaiModel);
|
||||
}
|
||||
break;
|
||||
case "q": // "-q"
|
||||
out = r2ai(args.slice(2).trim());
|
||||
break;
|
||||
case "Q": // "-Q"
|
||||
out = r2ai(args.slice(2).trim(), lastOutput);
|
||||
break;
|
||||
case "x": // "-x"
|
||||
var origColor = r2.cmd("e scr.color");
|
||||
r2.cmd("e scr.color=0");
|
||||
var cmds = decaiCommands; // +",axt";
|
||||
out = "[BEGIN]" + cmds.split(",").map(r2.cmd).join("\n") + "[END]";
|
||||
r2.cmd("e scr.color=" + origColor);
|
||||
r2ai("-R");
|
||||
out = r2ai("Analyze function calls, comments and strings, ignore registers and memory accesess. Considering the references and involved loops make explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in " + decaiHumanLanguage, out, true);
|
||||
break;
|
||||
case "d": // "-d"
|
||||
out = decaiDecompile(args, false, decaiCache);
|
||||
break;
|
||||
case "dd": // "-dd"
|
||||
out = decaiDecompile(args, false, false);
|
||||
break;
|
||||
case "D": // "-D"
|
||||
out = decaiDecompile(args, true, false);
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}
|
||||
if (out) {
|
||||
r2.log(out);
|
||||
}
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
r2.unload("core", command);
|
||||
r2.plugin("core", function () {
|
||||
function coreCall(cmd) {
|
||||
if (cmd.startsWith(command)) {
|
||||
var args = cmd.slice(command.length).trim();
|
||||
return r2aidec(args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
"name": command,
|
||||
"license": "MIT",
|
||||
"desc": "r2 decompiler based on r2ai",
|
||||
"call": coreCall,
|
||||
};
|
||||
});
|
||||
})();
|
||||
* On Swift binaries run...
|
||||
`};var E={decopipe:{use:!1},host:"http://localhost",port:"11434",baseurl:"",extraHeaders:[],api:"ollama",pipeline:"",commands:"pdc",yolo:!1,tts:!1,language:"C",humanLanguage:"English",deterministic:!0,debug:!1,timeout:180,http:"auto",think:"",apitype:"chat",useFiles:!1,contextFile:"",model:"",cache:!1,maxInputTokens:-1,prompt:T,lastOutput:""},r={...E,decopipe:{...E.decopipe},extraHeaders:[...E.extraHeaders]};function re(e){return(r2.cmd("-e dir.tmp").trim()??".")+"/"+e}function ne(e){if(!e.startsWith("~/"))return e;let t=r2.cmd("'%HOME").trim()||r2.cmd("'%USERPROFILE").trim();return t?t+e.slice(1):e}function H(e,t){r2.cmd("'mkdir -p "+ne(e)),r2.cmd("'touch "+ne(t))}function _(e){return(r2.cmd2("test -h").logs?.[0]?.message??"").includes("-fdx")?!0:r2.cmd("'test -vf "+e).startsWith("found")}function G(e,t){return e+" ".repeat(Math.max(0,t-e.length))}function h(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}function oe(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}function ie(e){let t=e.match(/```json\s*([\s\S]*?)```/);return t&&t[1]?t[1].trim():e}function ae(e){let t=e,n=t.indexOf("{");n!==-1&&(t=t.slice(n));let o=t.lastIndexOf("}");return o!==-1&&(t=t.slice(0,o+1)),t}function $(e){return btoa(e)}function se(e,t){let n=$(t);r2.cmd("p6ds "+n+" > "+e)}function k(e){let t=e;return t=t.replace(/<think>[\s\S]*?<\/think>/gi,""),t.split(`
|
||||
`).filter(n=>!n.trim().startsWith("```")).join(`
|
||||
`)}function J(e){r.debug&&console.log(e)}function le(e){let t={};for(let n of e.split(/\r?\n/)){let o=n.trim();if(!o||o.startsWith("#"))continue;let[i,...a]=o.split("=");if(!i||a.length===0)continue;let s=a.join("=").trim(),l=i.toLowerCase().replace(/_api_key$/i,"");t[l]=s}return t}var Ue=["DECAI_HEADERS","R2AI_HEADERS"];function _e(e){let t=r2.cmd("'%"+e).trim();return t!==""&&!t.includes("=")?t:""}function Me(e){return e.trim().toLowerCase()}function Ne(e){let t=e.indexOf(":"),n=e.indexOf("="),o=t!==-1&&(n===-1||t<n)?t:n;if(o===-1)return null;let i=e.slice(0,o).trim();return i===""?null:{name:i,value:e.slice(o+1).trim()}}function Te(e){return e.value===""?`${e.name}:`:`${e.name}: ${e.value}`}function ce(e,t){for(let n of e){let o=Ne(n.trim());o&&t.set(Me(o.name),Te(o))}}function W(e){let t=new Map,n=e.replace(/\\n/g,`
|
||||
`).split(/\r?\n/g).filter(o=>{let i=o.trim();return i&&!i.startsWith("#")});return ce(n,t),Array.from(t.values())}function ue(e){return e.join("\\n")}function A(...e){let t=new Map;for(let n of e)ce(n,t);return Array.from(t.values())}function He(){for(let e of Ue){let t=_e(e);if(t)return W(t)}return[]}function de(){return A(He(),r.extraHeaders)}function M(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function Ke(e){let{method:t,url:n,headers:o,payload:i}=e,a=A(["Content-Type: application/json"],o);if(r.http!=="curl"){if(r2.http)return r2.http(t,n,a.join(`
|
||||
`),i??"").body;if(r.http==="r2")throw new Error("r2.http is not available in this runtime")}let s=["curl","-s"];if(r.timeout>0&&s.push("--max-time",String(r.timeout)),a.forEach(l=>s.push("-H",M(l))),t==="POST"){if(!i)throw new Error("Payload required for POST requests");let l=r2.fdump(i);s.push("--data-binary","@"+M(l),M(n));let u=s.join(" ")+"; rm "+M(l);return J(u),r2.syscmds(u)}else{s.push(M(n));let l=s.join(" ");return J(l),r2.syscmds(l)}}function qe(e){let t=e.split(`
|
||||
`).filter(i=>i.trim()!=="");if(t.length<=1)return null;let n="",o=null;for(let i of t)try{let a=JSON.parse(i);if(a.error)return a;a.message?.content!==void 0&&(n+=a.message.content),o=a}catch{return null}return o?(o.message={role:"assistant",content:n},o):null}function pe(e){try{let t=Ke(e).trim();if(t==="")return{error:"empty response"};try{return JSON.parse(t)}catch{let n=qe(t);return n||{error:t,rawOutput:t}}}catch(t){return{error:t.message}}}function O(e,t){return pe({method:"GET",url:e,headers:t})}function fe(e,t,n){return pe({method:"POST",url:e,headers:t,payload:n})}function z(e){return e.split("_")[0].toLowerCase()}function me(e){if(!e.authKey)return;let t=Array.from(new Set([e.keyName,z(e.authKey)].filter(Boolean))),n;for(let o of t){let i=K(o,e.authKey);if(i[0])return i;!n&&i[2]!=="nope"&&(n=i)}return n||K(z(e.authKey),e.authKey)}function S(e){if(e)return typeof e=="string"?e:e.message||JSON.stringify(e)}function he(e){return r.baseurl||e.defaultBaseUrl}function ge(e,t=de()){return A(e,t)}function De(e){return e?["Authorization: Bearer "+e]:[]}function je(e){let t=["anthropic-version: 2023-06-01"];return e?A(t,["x-api-key: "+e]):t}function ye(e,t){switch(e.authStyle||"none"){case"bearer":return De(t);case"anthropic":return je(t);case"none":default:return[]}}function Be(e,t,n){let o=O(e,t),i=S(o.error);return i?(console.error(i),"error invalid response"):o.data?.map(n).join(`
|
||||
`)||""}function Fe(e,t){let n=new Set;return e.filter(o=>{let i=t(o);return n.has(i)?!1:(n.add(i),!0)})}function V(e){let t=me(e)?.[0]||null;return{baseUrl:he(e),headers:ge(ye(e,t))}}function Le(e){let{baseUrl:t,headers:n}=V(e),o=h(t,"v1/models");return Be(o,n,i=>i.id)}function Ge(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"api/tags"),n),i=S(o.error);return i?(console.error(i),"error invalid response"):o.models?.map(a=>a.name).join(`
|
||||
`)||""}function $e(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"v1/models"),n);return o.data?Fe(o.data,i=>i.name||i.id).map(i=>[G(i.name||i.id,30),G(""+(i.max_context_length||""),10),i.description||""].join(" ")).join(`
|
||||
`):""}var Je={openai:{buildPayload:(e,t)=>({stream:!1,model:e,messages:[{role:"user",content:t}]}),parseResponse:e=>{if(e.error)throw new Error(S(e.error)||"Unknown error");if(e.choices&&e.choices[0]?.message?.content)return k(e.choices[0].message.content);throw new Error("Invalid response format")},buildUrl:e=>h(e,"v1/chat/completions")},anthropic:{buildPayload:(e,t)=>{let n={model:e,max_tokens:5128,messages:[{role:"user",content:t}]};return Y()&&(n.thinking={type:"enabled",budget_tokens:4096},n.max_tokens=16e3),r.deterministic&&!Y()&&Object.assign(n,{temperature:0,top_p:0,top_k:1}),n},parseResponse:e=>{if(e.content&&Array.isArray(e.content)){let t=[];for(let n of e.content)n.text&&t.push(n.text);if(t.length>0)return k(t.join(`
|
||||
`))}throw e.error?new Error(S(e.error)||"Unknown error"):new Error("Invalid response format")},buildUrl:e=>h(e,"v1/messages")},ollama:{buildPayload:(e,t)=>{let n={stream:!1,model:e};return r.apitype==="generate"?n.prompt=t:n.messages=[{role:"user",content:t}],Y()?r.think==="true"||r.think==="1"?n.think=!0:n.think=r.think:n.think=!1,r.deterministic&&(n.options={repeat_last_n:0,top_p:1,top_k:1,temperature:0,repeat_penalty:1,seed:123}),n},parseResponse:e=>{if(e.error)throw new Error(S(e.error));if(typeof e.response=="string")return k(e.response);if(e.message?.content)return k(e.message.content);throw new Error(JSON.stringify(e))},buildUrl:e=>h(e,"api/"+r.apitype)},gemini:{buildPayload:(e,t)=>{let n={contents:[{parts:[{text:t}]}]},o={};return r.deterministic&&Object.assign(o,{temperature:0,topP:1,topK:1}),r.think!==""&&(X()?o.thinkingConfig={thinkingBudget:0}:o.thinkingConfig={thinkingBudget:8192}),Object.keys(o).length>0&&(n.generationConfig=o),n},parseResponse:e=>{if(e.candidates&&e.candidates[0]?.content?.parts){let n=e.candidates[0].content.parts.filter(o=>!o.thought&&o.text).map(o=>o.text);if(n.length>0)return k(n.join(`
|
||||
`))}throw e.error?new Error(S(e.error)):(console.log(JSON.stringify(e)),new Error("Invalid response format"))},buildUrl:(e,t,n)=>h(e,`v1beta/models/${t}:generateContent?key=${n}`),requiresUrlApiKey:!0}},q={anthropic:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"anthropic",authStyle:"anthropic",apiStyle:"anthropic"},claude:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"claude",authStyle:"anthropic",apiStyle:"anthropic"},openai:{defaultModel:"gpt-4o-mini",defaultBaseUrl:"https://api.openai.com",authKey:"OPENAI_API_KEY",authStyle:"bearer",apiStyle:"openai"},ollama:{defaultModel:"qwen2.5-coder:latest",defaultBaseUrl:"http://localhost:11434",authStyle:"none",apiStyle:"ollama"},ollamacloud:{defaultModel:"gpt-oss:120b",defaultBaseUrl:"https://ollama.com",authKey:"OLLAMA_API_KEY",keyName:"ollamacloud",authStyle:"bearer",apiStyle:"ollama"},zen:{defaultModel:"big-pickle",defaultBaseUrl:"https://opencode.ai/zen",authKey:"OPENCODE_API_KEY",keyName:"zen",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["big-pickle","glm-5","kimi-k2.5"]},gemini:{defaultModel:"gemini-2.5-flash",defaultBaseUrl:"https://generativelanguage.googleapis.com",authKey:"GEMINI_API_KEY",authStyle:"none",apiStyle:"gemini",hardcodedModels:["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite"]},mistral:{defaultModel:"codestral-latest",defaultBaseUrl:"https://api.mistral.ai",authKey:"MISTRAL_API_KEY",authStyle:"bearer",apiStyle:"openai"},xai:{defaultModel:"grok-beta",defaultBaseUrl:"https://api.x.ai",authKey:"XAI_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["grok-2","grok-beta"]},lmstudio:{defaultModel:"local-model",defaultBaseUrl:"http://127.0.0.1:1234",authStyle:"none",apiStyle:"openai"},deepseek:{defaultModel:"deepseek-coder",defaultBaseUrl:"https://api.deepseek.com",authKey:"DEEPSEEK_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["deepseek-coder","deepseek-chat"]},openrouter:{defaultModel:"openai/gpt-4o-mini",defaultBaseUrl:"https://openrouter.ai/api",authKey:"OPENROUTER_API_KEY",authStyle:"bearer",apiStyle:"openai"},groq:{defaultModel:"llama-3.3-70b-versatile",defaultBaseUrl:"https://api.groq.com/openai",authKey:"GROQ_API_KEY",authStyle:"bearer",apiStyle:"openai"}};function D(e){return q[e]}function Q(){return Object.keys(q)}function be(e){let t=D(e);if(!t)throw new Error(`Unknown provider: ${e}`);if(e==="mistral")return $e(t);switch(t.apiStyle){case"openai":case"anthropic":return Le(t);case"ollama":return Ge(t);case"gemini":return"";default:return""}}function X(){return r.think==="false"||r.think==="0"}function Y(){return r.think!==""&&!X()}function We(e,t,n){let o=e;return r.think!==""&&n==="openai"&&(X()?(o+=' Answers directly and concisely, without showing any thinking steps or internal reasoning. Never include phrases like "Let me think".',o+=" /no_think"):o="Think step by step and explain the reasoning process, When answering, first output your reasoning inside <think> and </think> tags, then give the final answer."+o),t?o:r.prompt+Ye()+o}function Ye(){return`
|
||||
.Translate the code into `+r.language+` programming language
|
||||
`}function ze(e,t,n,o){let i=r.model||e.defaultModel,a=We(n,o,e.apiStyle),s=me(e);if(t.requiresUrlApiKey&&s?.[1]&&e.authKey)return`Cannot read ~/.r2ai.${z(e.authKey)}-key`;let l=t.buildPayload(i,a),u=ye(e,s?.[0]||null);e.apiStyle==="ollama"&&u.push("Accept: application/x-ndjson");let v=ge(u),c=t.buildUrl(he(e),i,s?.[0]||void 0);try{return t.parseResponse(fe(c,v,JSON.stringify(l)))}catch(d){return"ERROR: "+d.message}}function ke(e,t){let n=D(r.api);return n?ze(n,Je[n.apiStyle],e,t):`Unknown value for 'decai -e api'. Available: ${Q().join(", ")}`}function Ve(){return Object.fromEntries(Object.entries(q).filter(([,e])=>e.authKey).map(([e,t])=>[e,t.authKey]))}function K(e,t){let n=r2.cmd("'%"+t).trim();if(!n.includes("=")&&n!=="")return[n.trim(),null,"env"];let o=e.toLowerCase(),i=N;if(_(i)){let s=r2.cmd("'cat "+i),l=le(s);if(o in l)return[l[o],null,"txt"]}let a="~/.r2ai."+o+"-key";if(_(a)){let s=r2.cmd("'cat "+a);return s===""?[null,"Cannot read "+a,"no"]:[s.trim(),null,"file"]}return[null,"Not available","nope"]}function xe(){H(w,N),r2.cmd("'ed "+N)}function Z(){Object.entries(Ve()).forEach(([e,t])=>{let n=K(e,t)[2];console.log(n," ",e)})}function ve(e){let t=D(e);if(!t){console.error(`Unknown provider: ${e}`);return}try{let n=be(e);n&&console.log(n),t.hardcodedModels&&t.hardcodedModels.forEach(o=>console.log(o)),e==="mistral"&&console.log("ministral-8b-latest"),!n&&!t.hardcodedModels&&console.log(t.defaultModel)}catch(n){let o=n;console.error(`Error listing models for ${e}:`,o.message),console.log(t.defaultModel)}}var ee=!1;function Qe(e){return e==="true"||e==="1"}function P(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=Qe(n)}}}function R(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=n}}}function Xe(e){let t=e.trim();return t==="chat"?"chat":t==="generate"?"generate":null}var Ee={pipeline:{get:()=>r.pipeline,set:e=>{r.pipeline=e;try{r.decopipe=JSON.parse(r2.cmd("cat "+e))}catch(t){console.error(t)}}},model:{get:()=>r.model,set:e=>{e==="?"?ve(r.api):r.model=e.trim()}},deterministic:P("deterministic"),files:P("useFiles"),think:{get:()=>r.think||"false",set:e=>{r.think=e}},apitype:{get:()=>r.apitype,set:e=>{let t=Xe(e);t?r.apitype=t:console.error("Invalid apitype. Use chat or generate.")}},debug:P("debug"),http:{get:()=>r.http,set:e=>{let t=e.trim();t==="auto"||t==="r2"||t==="curl"?r.http=t:console.error("Invalid http backend. Use auto, r2 or curl.")}},timeout:{get:()=>r.timeout,set:e=>{r.timeout=Math.max(0,parseInt(e,10)||0)}},api:{get:()=>r.api,set:e=>{e==="?"?console.error(Q().join(`
|
||||
`)):r.api=e}},lang:R("language"),hlang:R("humanLanguage"),cache:P("cache"),cmds:R("commands"),tts:P("tts"),yolo:P("yolo"),prompt:R("prompt"),ctxfile:R("contextFile"),baseurl:R("baseurl"),headers:{get:()=>ue(r.extraHeaders),set:e=>{r.extraHeaders=W(e)}},maxtokens:{get:()=>r.maxInputTokens,set:e=>{r.maxInputTokens=parseInt(e,10)||-1}}};function Ze(){let e={host:r.host,port:r.port,lastOutput:r.lastOutput};Object.assign(r,E,e),r.decopipe={...E.decopipe},r.extraHeaders=[...E.extraHeaders]}function et(e){let t=e.trim();return!t||t.startsWith("#")?null:t.startsWith("decai -e ")?t.slice(9).trim():t}function x(e){let t=e.indexOf("="),n=t===-1?e:e.slice(0,t),o=t===-1?void 0:e.slice(t+1),i=Ee[n];if(!i){console.error("Unknown config key");return}typeof o<"u"?i.set(o):console.log(i.get())}function we(){Object.entries(Ee).forEach(([e,t])=>{let n=t.get();console.log("decai -e "+e+"="+n)})}function Ae(){if(Ze(),!_(U))return;let e=r2.call("cat "+U);for(let t of e.split(/\r?\n/)){let n=et(t);n&&n[0]!="#"&&x(n)}}function Oe(){ee||(Ae(),ee=!0)}function Se(){H(w,U),r2.cmd("'ed "+U),Ae(),ee=!0}function p(e,t,n=!1){let o=(t||"").replace(/`/g,""),i=e.replace(/'/g,"");if(r.api==="r2"||r.api==="r2ai"){let s=re(".pdc.txt");se(s,o);let l=i.startsWith("-")?i:["-i",s,i].join(" "),u=r.baseurl||r.host+":"+r.port,v=h(u,"cmd/"+encodeURIComponent(l)),c=O(v,[]);return c.error?`Error: ${c.error}`:c.result||JSON.stringify(c)||"Cannot curl, use r2ai-server or r2ai -w"}if(i.startsWith("-"))return"";let a=i+`:
|
||||
`+o;return r.maxInputTokens>0&&a.length>r.maxInputTokens&&(a=a.slice(0,r.maxInputTokens)),ke(a,n)}function Re(e){let t=r2.cmd("e scr.color");r2.cmd("e scr.color=0");try{return e()}finally{r2.cmd("e scr.color="+t)}}function Pe(){let e="",t=n=>e+=" "+b+" "+n+`
|
||||
`;e+="Usage: "+b+` (-h) ...
|
||||
`,e+="Version: "+te+`
|
||||
`,t("-a [query] - solve query with auto mode"),t("-b [url] - set base URL (alias for decai -e baseurl)"),t("-d [f1 ..] - decompile given functions"),t("-dd [..] - same as above, but ignoring cache"),t("-dD [query] - decompile current function with given extra query"),t("-dr - decompile function and its called ones (recursive)"),t("-e - display and change eval config vars"),t("-E - edit and run decai.txt"),t("-h - show this help"),t("-H - help setting up r2ai"),t("-i [f] [q] - include given file and query"),t("-k - list API key status"),t("-K - edit apikeys.txt"),t("-m [model] - use -m? or -e model=? to list the available models"),t("-n - suggest better function name"),t("-p [provider] - same as decai -e api (will be provider)"),t("-q [text] - query language model with given text"),t("-Q [text] - query on top of the last output"),t("-r [prompt] - change role prompt (same as: decai -e prompt)"),t("-R - reset role prompt to default prompt"),t("-s - function signature"),t("-v - show local variables"),t("-V - find vulnerabilities"),t("-x[*] - eXplain current function (-x* for r2 script)"),r2.log(e.trim())}function j(e,t,n,o){if(n){let s=r2.cmd("anos").trim();if(s.length>0)return s}let i="";if(o){let s=r2.cmd("s");i+=`## Context functions:
|
||||
`;let l=r2.cmdAt("axff~^C[2]~$$",s);for(let u of l.split(/\n/g))i+=r2.cmd("pdc@"+u);r2.cmd("s "+s)}let a=t?" "+e:"";try{let s=e.slice(2).trim(),l="";r.contextFile!==""&&r2.cmd2("test -f "+r.contextFile).value===0&&(l+=`## Context:
|
||||
[START]
|
||||
`+r2.cmd("cat "+r.contextFile)+`
|
||||
[END]
|
||||
`);let{body:u,count:v}=Re(()=>{let d=`## Before:
|
||||
`,y=0;for(let g of r.commands.split(",")){if(g.trim()==="")continue;let C=t||s.trim().length===0?g:g+"@@= "+s,I=r2.cmd(C);I.length>5&&(d+="Output of "+g+`:
|
||||
[START]
|
||||
`+I+`
|
||||
[END]
|
||||
`,y++)}return d+=`## After:
|
||||
`,{body:d,count:y}});if(v===0){console.error("Nothing to do.");return}let c="";if(r.decopipe.use){let d=r.model;try{let y=r.decopipe[r.decopipe.default],g=l+u;for(let C of y.pipeline){C.model&&(r.model=C.model);let I=C.query+". "+y.globalQuery;c=p(I,g,!0),r.debug&&console.log(`QUERY
|
||||
`,I,`
|
||||
INPUT
|
||||
`,g,`
|
||||
OUTPUT
|
||||
`,c),g=c}c=g}finally{r.model=d}}else{let d=a;l+=u+i,c=p(d,l,!1),r.lastOutput=c}return n&&c.length>1&&r2.call("ano=base64:"+$(c)),c.startsWith("```")&&(c=c.replace(/```.*\n/,"").replace(/```$/,"")),c.trim()}catch(s){let l=s;console.error(l,l.stack);return}}function tt(){let e=Re(()=>"[START]"+r.commands.split(",").map(o=>r2.cmd(o)).join(`
|
||||
`)+"[END]"),n=p("Analyze function calls, references, comments and strings, loops and ignore registers and memory accesses. Explain the purpose of this function in a single short sentence. /no_think Do not introduce or argue the response, translation of the explanation in "+r.humanLanguage,e,!0).trim().split(/\n/g);return n[n.length-1].trim()}function nt(){let e=r.language,t=r2.cmd("afv;pdc");r.language="C";let n="'afs "+p("analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the return. Do NOT print the function body, ONLY output the function signature, like if it was going to be used in a C header",t,!1),o=n.indexOf("{");return o!==-1&&(n=n.substring(0,o)),r.language=e,n}function rt(e,t){let n=[];for(;;){let o=L.auto;if(n.length>0){o+=`## Command Results
|
||||
|
||||
`;for(let a of n){let s=JSON.parse(a);o+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}o+=`
|
||||
|
||||
## User Prompt
|
||||
|
||||
`+e,r.debug&&console.log(`#### input
|
||||
`,o,`
|
||||
#### /input`),console.log("Thinking...");let i=p("",o,!0);r.debug&&console.log(`#### output
|
||||
`,i,`
|
||||
#### /output`);try{let a=JSON.parse(ae(ie(k(i))));if(a.action==="r2cmd"||a.action==="response"||a.action===a.command){let s=a.command||"";a.reason&&(console.log("[r2cmd] Reasoning: "+a.reason),r.tts&&(r2.syscmd("pkill say"),r2.syscmd("say -v Alex -r 250 '"+a.reason.replace(/'/g,"")+"' &"))),console.log("[r2cmd] Action: "+a.description),console.log("[r2cmd] Command: "+s);let l=s;r.yolo||(l=ot(s,t)),console.log("[r2cmd] Running: "+l);let u=r2.cmd2(l),v=u.logs?u.logs.map(y=>y.type+": "+y.message).join(`
|
||||
`):"",c=(u.res+v).trim();console.log(c);let d=oe(c);r.debug&&console.log(`<r2output>
|
||||
`,d,`
|
||||
<(r2output>`),n.push(JSON.stringify({action:"response",command:l,description:a.description,response:d}))}else if(a.action==="reply"){console.log(`Done
|
||||
`,a.response);break}else{console.log(`Unknown response
|
||||
`,JSON.stringify(i));break}}catch(a){let s=i.indexOf('response": "');if(s!==-1){let l=i.slice(s+12).replace(/\\n/g,`
|
||||
`).replace(/\\/g,"");console.log(l)}else console.log(i),console.error(a);break}}}function ot(e,t){for(;;){let n=r2.cmd("'?ie Tweak command? ('?' for help)").trim();if(n==="q!"){console.error("Break!");break}if(n==="?"){it();continue}else if(n.startsWith(":")){console.log(r2.cmd(n.slice(1)));continue}else if(n.startsWith("-e")){t(n);continue}else{if(n==="!")return"?e do NOT execute '"+e+"' again, continue without it";if(n.startsWith("!")){console.log(r2.syscmd(n.slice(1)));continue}else{if(n==="q")return"?e All data collected!. Do not call more commands, reply the solutions";if(n){let o=n.indexOf("#");return o!==-1?n.slice(0,o).trim():n}else return e}}}return e}function it(){console.log(" '!' do not run this command"),console.log(" '!c' run system command"),console.log(" 'q' to quit auto and try to solve"),console.log(" 'q!' quit auto without solving"),console.log(" 'c # C' use given command with comment"),console.log(" ':c' run r2 command without feeding auto"),console.log(" '-e' set decai configuration variables")}function at(e){return e.slice(2).trim()}function B(){return r2.cmd("afi.").trim().length>0}var F="Cannot find function at current offset";function Ce(e,t){if(e===""||!e.startsWith("-")){Pe();return}let n="",o=e[1],i=at(e);switch(o){case"H":console.log(L.decai);break;case"a":rt(i,t);break;case"m":{i==="="?x("model="):i?x("model="+i):x("model");break}case"n":case"f":{if(!B()){n=F;break}n=r2.cmd("axff~$[3]");let a=r2.cmd("fd.").trim().split(/\n/).filter(s=>!s.startsWith("secti")).join(",");n=p("give me a better name for this function. the output must be: 'afn NEWNAME'. do not include the function code, only the afn line. consider: "+a,n,!1).trim(),n+=" @ "+r2.cmd("?v $FB").trim();break}case"v":{if(!B()){n=F;break}n=r2.cmd("afv;pdc"),n=p("guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands",n,!1);break}case"i":{let a=i.split(/\s+/).filter(Boolean);if(a.length>=2){let s=a[0],l=a.slice(1).join(" "),u=r2.cmd("cat "+s);console.log(p(l,u,!0))}else console.log("Use: decai -i [file] [query ...]");break}case"p":{i?x("api="+i):Z();break}case"r":{i?r.prompt=i:console.log(r.prompt);break}case"R":r.prompt=T;break;case"s":if(!B()){n=F;break}n=nt();break;case"V":n=p("find vulnerabilities, dont show the code, only show the response, provide a sample exploit",r.lastOutput,!1);break;case"K":xe();break;case"E":Se();break;case"k":Z();break;case"b":{i?x("baseurl="+i):console.log(r.baseurl);break}case"e":{i?x(i):we();break}case"q":try{n=p(i,null,!0)}catch(a){let s=a;console.error(s,s.stack)}break;case"Q":n=p(i,r.lastOutput,!1);break;case"x":if(!B()){n=F;break}n=tt(),(e[2]==="*"||e[2]==="r")&&(n="'CC "+n);break;case"d":e[2]==="r"?n=j(e.slice(2),!0,r.cache,!0)||"":e[2]==="d"?n=j(e,!1,!1,!1)||"":e[2]==="D"?n=j(e,!0,!1,!1)||"":n=j(e,!1,r.cache,!1)||"";break;default:Pe();break}return n||void 0}function Ie(e){Oe();let t=Ce(e,Ie);return t&&r2.log(t),!0}function st(){r2.unload("core",b),r2.plugin("core",function(){function e(t){if(t.startsWith(b)){let n=t.slice(b.length).trim();return Ie(n)}return!1}return{name:b,license:"MIT",desc:"r2 decompiler based on r2ai",call:e}})}st();
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
PACKAGE=decai
|
||||
DEPENDS=radare2
|
||||
SECTION=user/shell
|
||||
PRIORITY=optional
|
||||
MAINTAINER=pancake <[email protected]>
|
||||
VERSION=$(shell git tag | tail -n 1)
|
||||
# arch
|
||||
UNAMEM=$(shell uname -m)
|
||||
ifeq ($(UNAMEM),x86_64)
|
||||
ARCH=amd64
|
||||
else
|
||||
ARCH=arm64
|
||||
endif
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
AI based decompiler plugin for radare2
|
||||
r2js plugin for radare2 that provides several features for reverse engineers, from autonaming functions, improve decompiling outputs, find bugs or explain functions to automated crackme solving using local and remote models if API keys are provided..
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
include ./CONFIG
|
||||
|
||||
UNAME=$(shell uname)
|
||||
SUDO?=sudo
|
||||
DEPENDS=
|
||||
CROSSARCH=x64
|
||||
R2CFG_FLAGS?=
|
||||
PWD=$(shell pwd)
|
||||
PACKAGE_DIR?=${PWD}
|
||||
|
||||
R2_VERSION=$(shell r2 -qv)
|
||||
|
||||
DOCKCROSS=$(PWD)/../dockcross
|
||||
R2PLUGDIR=/usr/lib/radare2/$(R2_VERSION)
|
||||
|
||||
all: root
|
||||
$(SUDO) rm -rf control data
|
||||
$(MAKE) clean
|
||||
mkdir -p data
|
||||
cp -rf root/* data
|
||||
$(MAKE) control
|
||||
$(MAKE) deb
|
||||
|
||||
root:
|
||||
mkdir -p root/$(R2PLUGDIR)
|
||||
cp -f ../../decai.r2.js root/$(R2PLUGDIR)
|
||||
|
||||
purge: clean
|
||||
rm -rf root
|
||||
|
||||
summary:
|
||||
echo $(VERSION)
|
||||
|
||||
include deb.mk
|
||||
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
# Create .deb without using dpkg tools.
|
||||
#
|
||||
# Author: Tim Wegener <[email protected]>
|
||||
#
|
||||
# Use 'include deb_hand.mak' after defining the user variables in a local
|
||||
# makefile.
|
||||
#
|
||||
# The 'data' rule must be customised in the local make file.
|
||||
# This rule should make a 'data' directory containing the full file
|
||||
# layout of the installed package.
|
||||
#
|
||||
# This makefile will create a debian-binary file a control directory and a
|
||||
# a build directory in the current directory.
|
||||
# Do 'make clobber' to remove these generated files.
|
||||
#
|
||||
# Destination:
|
||||
# PACKAGE_DIR - directory where package (and support files) will be built
|
||||
# defaults to the current directory
|
||||
#
|
||||
# Sources:
|
||||
# SOURCE_DIR - directory containing files to be packaged
|
||||
# ICON_SOURCE - 26x26 icon file for maemo
|
||||
# DESCR - description with summary on first line
|
||||
# preinst, postinst, prerm, postrm - optional control shell scripts
|
||||
|
||||
# These fields are used to build the control file:
|
||||
# PACKAGE =
|
||||
# VERSION =
|
||||
# ARCH =
|
||||
# SECTION =
|
||||
# PRIORITY =
|
||||
# MAINTAINER =
|
||||
# DEPENDS =
|
||||
#
|
||||
# SOURCE_DIR =
|
||||
# ICON_SOURCE =
|
||||
# (ICON_SOURCE is optional)
|
||||
|
||||
# *** NO USER CHANGES REQUIRED BEYOND THIS POINT ***
|
||||
ifeq ($(shell uname),Darwin)
|
||||
MD5SUM=md5
|
||||
else
|
||||
MD5SUM=md5sum
|
||||
endif
|
||||
|
||||
GAWK=awk
|
||||
PACKAGE_DIR=$(shell pwd)
|
||||
CONTROL_EXTRAS ?= ${wildcard preinst postinst prerm postrm}
|
||||
|
||||
${PACKAGE_DIR}/control: ${PACKAGE_DIR}/data ${CONTROL_EXTRAS} DESCR \
|
||||
${ICON_SOURCE}
|
||||
#rm -rf $@
|
||||
mkdir -p $@
|
||||
ifneq (${CONTROL_EXTRAS},)
|
||||
cp ${CONTROL_EXTRAS} $@
|
||||
endif
|
||||
# Make control file.
|
||||
echo "Package: ${PACKAGE}" > $@/control
|
||||
echo "Version: ${VERSION}" >> $@/control
|
||||
echo "Section: ${SECTION}" >> $@/control
|
||||
echo "Priority: ${PRIORITY}" >> $@/control
|
||||
echo "Architecture: ${ARCH}" >> $@/control
|
||||
ifneq (${DEPENDS},)
|
||||
echo "Depends: ${DEPENDS}" >> $@/control
|
||||
endif
|
||||
echo "Installed-Size: ${shell du -s ${PACKAGE_DIR}/data|cut -f1}" \
|
||||
>> $@/control
|
||||
echo "Maintainer: ${MAINTAINER}" >> $@/control
|
||||
printf "Description:" >> $@/control
|
||||
cat DESCR | ${GAWK} '{print " "$$0;}' >> $@/control
|
||||
#ifneq (${ICON_SOURCE},)
|
||||
# echo "Maemo-Icon-26:" >> $@/control
|
||||
# base64 ${ICON_SOURCE} | ${GAWK} '{print " "$$0;}' >> $@/control
|
||||
#endif
|
||||
# Make md5sums.
|
||||
cd ${PACKAGE_DIR}/data && find . -type f -exec ${MD5SUM} {} \; \
|
||||
| sed -e 's| \./||' \
|
||||
> $@/md5sums
|
||||
|
||||
${PACKAGE_DIR}/debian-binary:
|
||||
echo "2.0" > $@
|
||||
|
||||
${PACKAGE_DIR}/clean:
|
||||
rm -rf ${PACKAGE_DIR}/data ${PACKAGE_DIR}/control ${PACKAGE_DIR}/build *.deb
|
||||
|
||||
${PACKAGE_DIR}/build: ${PACKAGE_DIR}/debian-binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data
|
||||
rm -rf $@
|
||||
mkdir $@
|
||||
cp ${PACKAGE_DIR}/debian-binary $@/
|
||||
cd ${PACKAGE_DIR}/control && tar czvf $@/control.tar.gz *
|
||||
cd ${PACKAGE_DIR}/data && \
|
||||
COPY_EXTENDED_ATTRIBUTES_DISABLE=true \
|
||||
COPYFILE_DISABLE=true \
|
||||
tar cpzvf $@/data.tar.gz *
|
||||
|
||||
# Convert GNU ar to BSD ar that debian requires.
|
||||
# Note: Order of files within ar archive is important!
|
||||
${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb: ${PACKAGE_DIR}/build
|
||||
ar -rc $@ $</debian-binary $</control.tar.gz $</data.tar.gz
|
||||
#sed -e 's|^\([^/]\+\)/ \(.*\)|\1 \2|g' $@tmp > $@fail
|
||||
#rm -f $@tmp
|
||||
#mv $@fail $@
|
||||
|
||||
.PHONY: data
|
||||
data: ${PACKAGE_DIR}/data
|
||||
|
||||
.PHONY: control
|
||||
control: ${PACKAGE_DIR}/control
|
||||
|
||||
.PHONY: build
|
||||
build: ${PACKAGE_DIR}/build
|
||||
|
||||
.PHONY: clean
|
||||
clean: ${PACKAGE_DIR}/clean $(EXTRA_CLEAN)
|
||||
rm -f debian-binary
|
||||
|
||||
.PHONY: deb
|
||||
deb: ${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb
|
||||
|
||||
|
||||
clobber::
|
||||
rm -rf ${PACKAGE_DIR}/debian_binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data ${PACKAGE_DIR}/build
|
||||
|
||||
push:
|
||||
scp *.deb radare.org:/srv/http/radareorg/cydia/debs
|
||||
|
||||
mrproper: clean
|
||||
rm -rf root
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"use": true,
|
||||
"default": "pipeTest",
|
||||
"_default": "asciiStack",
|
||||
"asciiStack": {
|
||||
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
|
||||
"pipeline": [
|
||||
{
|
||||
"model": "qwen2.5-coder:latest",
|
||||
"query": "stack frame layout for this function, output a table in ascii art"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pipeTest": {
|
||||
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
|
||||
"pipeline": [
|
||||
{
|
||||
"model": "qwen2.5-coder:latest",
|
||||
"query": "resolve stack variables and arrays size without using malloc"
|
||||
},
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "remove boilerplate and unnecessary code, replace goto with if/else blocks"
|
||||
}
|
||||
],
|
||||
"commentedOut": [
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "use better types and names for variable and arguments"
|
||||
},
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "inline all the functions into a single one and make the code more readable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loaded 100 of 213 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user