From 8ae812660ea804a75d03976889e6354c2617df90 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Mon, 15 Jun 2026 23:55:29 -0700 Subject: [PATCH] feat: add one-click Factory.ai Droid setup Add a Factory integration to the macOS app's Agent Setup pane that writes Composer 2.5 and Composer 2.5 Fast as Factory custom models into ~/.factory/settings.json (with a backup), mirroring the existing OpenCode and Codex installers. Adapted from DroidProxy (https://github.com/anand-92/droidproxy). --- README.md | 12 +++ index.html | 10 ++- .../Sources/CursorAPI/ContentView.swift | 2 + .../CursorAPI/Resources/AgentIcons/README.md | 1 + .../Resources/AgentIcons/factory-dark.png | Bin 0 -> 11296 bytes .../Resources/AgentIcons/factory.png | Bin 0 -> 20812 bytes .../CursorAPICore/AgentProvisioner.swift | 80 +++++++++++++++++- .../Sources/CursorAPICore/Models.swift | 3 + .../AgentProvisionerTests.swift | 67 +++++++++++++++ public/factory-logo.svg | 1 + public/setup.md | 40 ++++++++- 11 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory-dark.png create mode 100644 macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory.png create mode 100644 public/factory-logo.svg diff --git a/README.md b/README.md index f6c9098..c4682da 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,18 @@ Token usage is estimated from character counts because Cursor's stream does not Use the app's **Agent Setup** pane to install the local OpenCode provider. The configured provider points at the local base URL, not the hosted Worker. +## Factory + +Use the app's **Agent Setup** pane to add Composer 2.5 and Composer 2.5 Fast as +Factory.ai Droid custom models. The installer writes `customModels` entries into +`~/.factory/settings.json` (backing the file up first) that point Droid at the +local OpenAI-compatible base URL. Restart Factory or open a new session to see +them in the model picker. + +The one-click Factory flow is adapted from +[DroidProxy](https://github.com/anand-92/droidproxy) — thanks to DroidProxy for +the approach of writing Factory custom models from a local proxy app. + ## Local development ```bash diff --git a/index.html b/index.html index f42902a..3520268 100644 --- a/index.html +++ b/index.html @@ -78,7 +78,7 @@

Run a small local server that makes Cursor Composer models available to OpenCode, - Codex, VS Code extensions, and any OpenAI-compatible client. + Codex, Factory, VS Code extensions, and any OpenAI-compatible client.

@@ -144,6 +144,10 @@

Local server

Codex Configured
+
+ Factory + Configured +
@@ -272,8 +276,8 @@

The hosted API is moving out of the request path.<

The local app is the better long-term experience: your API key stays on your machine, - agent tools run against your real project folders, and OpenCode or Codex can connect to - a normal OpenAI-compatible localhost URL. + agent tools run against your real project folders, and OpenCode, Codex, or Factory can + connect to a normal OpenAI-compatible localhost URL.

diff --git a/macos/CursorAPI/Sources/CursorAPI/ContentView.swift b/macos/CursorAPI/Sources/CursorAPI/ContentView.swift index 8138ee0..a401ac3 100644 --- a/macos/CursorAPI/Sources/CursorAPI/ContentView.swift +++ b/macos/CursorAPI/Sources/CursorAPI/ContentView.swift @@ -1707,6 +1707,8 @@ private extension AgentIntegrationID { return "kilo" case .pi: return "pi" + case .factory: + return "factory" case .continueDev: return "continue" case .aider: diff --git a/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/README.md b/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/README.md index 334b14e..0544235 100644 --- a/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/README.md +++ b/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/README.md @@ -9,4 +9,5 @@ Integration icons are bundled so API for Cursor can render them offline. - `continue.png`: Continue extension icon from `continuedev/continue` - `aider.png`: Aider web app icon from `Aider-AI/aider` - `roo.png`: Roo Code extension icon from `RooCodeInc/Roo-Code` +- `factory.png`: Factory.ai mark, rendered from the logo bundled in DroidProxy (`anand-92/droidproxy`). The one-click Factory setup is adapted from DroidProxy. - `../cursor-logo.png`: Standard Agents mark rendered white on black for the app chrome diff --git a/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory-dark.png b/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4cada48bca539408f801635cc62f989fc8ce97 GIT binary patch literal 11296 zcmZ{K2T)T{wCzm@5SkErs3Dt;5a|dgD!nUMC_$u1 zi>QnX~U+Ywvx}Ig^{@?3HACO`nOLhaLa`CPM?A8vp>J zm>>X3LmBJ?%iSmg#9hlo3ji9@8IGN)Dc1-WgBvCQ5FrWxxC8(=q_}WP01zSz04q)a zpqdK+T>gdc%+x7>j;pnyn~4b^L9w9#=)NZap;-TF@KD|WKve<)s3;@opRWY`e^C%n zLiIoPKcf2zk0FKj#M9h5*xJNc)y2>Ef-}bNw(Es3U;lpuKs`*AV*0uUJ0rq;eeMLQ zhG`)GgP}^X|G8z5i2tAjdut%AO-vEmeu1tCr37ejC!OmgQcY;v=ndJZG(QyrO z3H0<2_Vl}h_?OrDwqHoF1`_$N(0{-F^FCd}JpZTCouL1k7G;7m{}36u3$il*otqM> z{?Dsw>KW$hW3A)q>v|`MQbSWwPG0>#`2P>`KNbI1q|N^~@_!-!6{#-sZvy{oLjSp1 z|KX)9mnOZs%zrPvCjA>311kVH-)N|#WgZ6l^CY?XM{|wt>_yL$GsoXbzUw>cYpPE< zGys-J17t7$g|wM)Js^eqqWRtNbRB!-`uZN>3ONgUe0a4c=LY?QB9>+=<3L+(F4Q=I))dggf zPH#jB)kB>QD4DyVQsA(Lzd0)m6l8#w6D*_W1S2G;c9=U-5NpkPmM}SRN1OGYXyw^n z$Jq(D)sbV&$qrZZC?{9}Sr0{gk~ljc#zY_d0~umFPLimhWWSiB?=l@{r`Rt(>%4lo z(FdFJQT>;`?GGGk;Z=j__Aj6A&vHS@l}Qjw-TL&$eK)aj4zYC_kP?&8(GNj_*9S{ScQc^9Uw`R8YB~vo|+#X@Le>&cA zI{#+&-S13!PS>{PKHF0sX}k0?)+!UMW8DtV=LcM_)qAnUt{&jTfc5+aXmGG;bIa1y zyL1VbzL3{Jl$|J?-3s8ikKAhM_hJEtBg)2YY{%J-Yy9PM7wQ=TrNPPxi(4O%xa;C+ z%urLlcGq~PFacTY&1Uz@K?NC)-9CE52=^@JHn@IN%1D-ON_aQx$oC;{kI_x_Xjw5L)Q<_h|TJYg^b2#*?YSXevoZ5E?$ zAAAc%8x*c{fmX@dG#K8|i!r~)t!)FySp7@c;2J2I$@>^) zR6e4~mKOsf&?q+kWUPBPheVT91CJ#i!N!sL>L@3Gi46LO{*U36NL9eg`4FRNjqQIex)s_Q&&PFYF|l>A=A z&TWWMU#Nbs5}?+EWRsvs*N8N`*PIaCR22yprU4Ro%H*Z_zcygO@K^1Z*GPL;-KcTV zOK!zP_kN|FK=^ zXD;`;8l~phqA1acyd2+VLe3PN=x~vCf*S*}$`N4@$#33YcxsV3LiW_lg>% z9W(q9{LD@|ksz6ota*$gw05T3ykTY~SJg>RJ^>4!D12i1VYQo%9rdO|fZr&P(O;l?$h?Cu54MZ$w>J zRB`d;z?HT<4!XX(q(bewecBf_ajVY3f(h4V@bk}Tr#BQ>?1EP_(K>sNHdX>q$Pe58 z{MgvJL2gM^nq4a-j=etifpr24|55E;+e1KIZzaQGJ`!$0ScS+Uj~q$7^5_dn(E*0L zEb!K+s$tZi2Rn6qSH|a|hf`j~7Sw~>_I8772$as?%M}0_rV4M31N=1&2$K<*m`}*Y ziVE=KPi{3}LWkaE=g}bMmn^Y;fLanuk@-KO`mmVxUSRW}gp(V%kHA*_IfIPZnt1IN z4U^JIWZZ;d-m1aXivWyz71gV-KcR}DdK|LeNq@S}pptQ^buKv|T2T4Br57FBFgSvX zS4qlM?TbtER=OBa2r0G6&;9Y`QWJ9;)!(H7sf>pCT;*BgRCL3y7~xC%KJmrv$ZKg0 zP0*)e@W%s?`rfp@keuBx@P;xK4PW=K{P&{PpF@&ML+wV@0vz4|RHnV|i2Q zq5z}wP&v0p@G=buFIOY)$CTWzU;*m8@tu%>eyEfI^1Dl*jm(gI*r0!I;WMlASS9%{ z#7S|2k+H_d%8q)lgkjjx2>8(D)m0S@5hAkUYlP#gj^t|<1T8@FPQ`H6a}gT(K=rwD zsSM|(Njd)%9x!^bXvRhP%O%vnnA;hO)c>VbI8MLotCpB$h-kiM#pEepxo{1eP6^Qd-ml#il5$S~r{G z;a>T#H!JdHR~;7i$Q_p~{cel}&i)>+6Fe)uS;Mp&tg1>2+b9*4f_Vj~<wb>!D+s%M4u`bkEFF|+L6NaIN&2`=?XvyPq;XOA}SbVlV4OgiUX9 ztSxuFWe20tL@LHUEl6zK#S8%Gx~rJt9umEf|5nJ634{yatg!X}klk4I;;fmNLW+?@ z71QaEUS@CX_RJJf-R$Pl!Fzu70wX5GExZ`hKTM@lC9%vgA`_&=-HR1_Yy@G$HpL_3 z1i(F6Id8)!Lov-ism~@Y>(A8u1gUqQxsJKPtj-IFOu95w@bu=WHd^kc@5cK1xM#pX z7mHGwblww9&TvGwvFMMlLbLWJfZawvK0_{d;7Y6hR?jLd31R0hIn6%4HW!FyNWI)AGdwmrTfIgzH#yvuTmVut8cPYt-V+mY+;16 zEyX^zbS%tIT{U6xP6EUWTl#|UZ#o_}>h~JRGGvZn%EGVaY4kQeT`YnhJ^U!8X)W1d z0Q`!@J(D&DFs=#Qqq)2#B=df|`OcrgMDYWSt*Z`I(eo25`rcpy4U8l#=6zOn(EyM_ zCj9Bfg@-9a>2yw1jQLCHN+1jf^>Y)dh^r*|G`TykT2~u%sy>A+S!8y~Zwv`gu|A-A zZpA5`-nLf}lMASW%09VGxa6v-+CP5fkIxo1%{=jb;7jAE`sitkgE{ZW!`TpsKjsyQ zsBJqUfmq?=G`8HG(XViSBm~$0v!MzGsx@=q~KOORHMc{GP#C=@vQVvTlP4Ps`}R#vV|F2EbWlchnnklTxjhe{I)Z z-`&lg$%EVv((k4Ep7*z=fj@V{A=-RH|8^h@?Hs_^r=$i$6z2f!ui%|l1t$!Bf&tLx z$Hxu@7>V>-DB^ui(5kibT_oYN&vc{1TytsTZ}wT8Nw$t{DozD;^R#xP1H}K=rzTd= z3jvz4sqKq0&a0^=*~uxWyuF^*Sru8tMkyw@B50`b^+P~*rF`d_D9*|yONT&JZBP-S zcz-WKho&%FISgaRjFlRIiWf5#+akp)Y2pT=ZC&LW21sb&?oPeTqt6$_yy^p#vQgRx_C0+w2My zH#%XC1BVwVcR|)65kYgZs{!8vGq;8pjBVL=MZxuAZwTF!gv`_FlI;tM!q>A)y;{ex z^?CC@r!B2knTqypEUS17&vA#O)v$`cbhIEj%r^A_sEpK!pgY-?tXeX?V)y{->r`sY zlt+c|k;`?Wx&XiY(L?qIM;)TEOtGwE1b}D zguRuC5<>CHw|IT{p#6b7@0r3cWE))kHfiEBG1*Y2ZR5#mwPU}7uMi)L5bE4!CL$K? z?2v1=YR%f|Axg_EuP{8C5B)r21UTLMQ_B+()-x6+-I&!=`(xnx=c+M3Z&#Ifj_pio zd@Ww*Avn}1O<^~@4(-I4it5d3s9W<5xupYWvVc1CeNC5BYsV7RKdh`48Q@E1Qb>YN zMraG&m?JluCKdNvku=_QnGUeuPb!6R3&G&GB1vHn)I`Ve1Z` zN-AwEx&7fCIwkG4ejqQY#yZfPnstmUx^_9l%0zvI0@QA<7On37v|Gf!QZsQ*`MYCaYIDKAR~ga5bNYAViR)D{Sz9V+SCn(@L-V}-YnK6siIiEhKNE-7 zl1znv$qswoc#l#j;G;djT2TjDN;7yBf^Jnq0KEG<`C-zIhBlPg<}FPW65G7pF|D&U z%5c;_HjTC0zK#Oe3&P?4Gy;JG;>v7k3EDfsq6-*QJd!~^oENmz&}oCiX0ttNa^L0h zbP;PN@hb7_;@u~n!*Frpzm&N%rGIU^_v_YbVWePsD%L|)+8bS;5UfV=z(lhJEht3q z9M;8M%4N6bN0Ii0Ic0y8BtXz2Re%IfKzE6DQzQ}$) z%fROySpeaQN;8D~wpcEKqgR?I3{lq18T5)LGdEXpe?%n<@E2z9F`d@K2dt;~+HtTT zrWxTQ$tv3`ct-EeUT>;kXZsAGxNTQkNm47^f61g(fK(l2N51{!`xlukAeW_iOO<7B z2P(c$;n^R&H1Sm>FWE6)#8mo)(n+-v;=Oz`ln_zuKi59UcwZg)RBb=OUwM*PtE~zm zv-mi4FQ@-?cQ=Of@DI3CYgB^cGE;Fb+KFv{>Ccw{$$OHnHr%#yCAwrF~H6*dd_01NCnW`bFq>n8u<`(jNe2vuA1^Km5I- zGejrZ`N(ceUMm9AxF_B9Am z$d#%8qmc!x0qH&3byY7*_6|Eo-`I@n z=J)SrkRh}T70!aJzkKM{CLe0O0&$7Ht}lHF2p=O8-k%3C5JhVZezxwkEF5CRgrDg>;7o{O;p??(K&m7J+G6=?dIJ=4#+ ze{vXCYS#0A_c6(3+vXdmIkCJ9=1~*KxJ@dK?#qzYR=L0UhNJ+{Ip0E6TqX=2el(YN zThD^KF&0VQq7&zv`E_jN;_6^k?_I32J8jMK>;*m9p)V#|c)SqkyOS;ULZn;)WZpk| z$X(sX!qVvj8nxY<{A^^PE1+yDQ0@ zV=5s0^T3_`q`0D!rG*MGJ~WXw?iLhrgBloS)Z_nSUI|r)sM;=AZe^+rlq`n5{Kvu4 z2>KnenD%6hs9!Ul(FHC$l&x4~_MzXjJIl;C6KUuMC-FmvdH{bpx86}07Z`0bw&MWC znYk+|N9=eh)ANq9Gg}tFyO^sn;7Izur3dg{S(#F(3@NZNz9RyP6ZCfrw^i*&e-H13 z8Hd$wCcMO1B?>-)dCTbv1`|T1Q=_*9vzFe$04JSFoGuU(QffB5X=QlZz29kZf9=r6 z3}2l2u{OUFXBIu31;{pEeJ<(r=A80x8-im-qor9)ThJgJ%=bNWnf=&Bcv(U6Fro_h zbsGY^D*}-B>QN^a$bjPoJtdfs?Z7}rm@idyLQwj*narhnX=X@GjD1#xRVbJM19}f@ z&NuR>rE9mM<$-`Gyxa=Lss(l zlJpdXE)C@)ugjRX?09Z*n+MFP^s`D3RA6&l3yx&qow4bH7b2wqxU; z-KzyB!hrr8)YdcZU1`dsA7k15o3}$Hf5sA@@LaogFsXq30TQePBFC=cH!~fS?;hCU z6tu%nO`%F{U85q*aTA@VEVZW-$Xg<8rpQYFC`iGUV5hW zUD3{-`MZ>_RrI-WMHbZ7S6y>6qT8M%skCUtxNU^|p#?k9bh^k$Gey++xi^A9sq419 zQJuAlmN(4-+=s8%f9J$USb(@o7$X?8Y?um1m>1FQ?-iw)COTgHR2y?-WEIt@#GRJ_ z*hR~`Bfw;jnMQg%V4pEYLQS3THkx0`~p@*s+uNwh?wbJ#$NHT`(&0#f-;61hp4Bv(eMnzmKv^uvMaJ$M!M2>;Bk9?kK6`^bt zCmHfGMtCI4g^&OG{aSh4d@KTj&?ntA)v6=R{8s2v3CbD=L=BPdS8 zjsc0b^y#sjoJcmQ8J1qtr`_JXD2sy%;a8PL6@c^iZ~m1cJR$+qy}~$;P`ZdY%g;u4 zm)A+ycI}_=(C?w^9d~HGVV%t}yY8;U_cF``sm)f(1Hg>Q#1K$smhy~37{s{JGI)ND zF_(;Q&mJiqW*>=42QFT_j&!=_BaJNYHdWo8%$#biqsm~?uscq-`6>;Dr}z{k}X06?g)D;Jc;D zx@8N+g%fmiN~C-#hkJn$n#p`w(iu6@Ji$e8Yoa>&!;xW5RCPaw=MJl^3U}80nG(Cg z#8t6fEP0@^=>HuTllKyyQU%7bQGEWp<7vZlXj3osbVgNeKvEA2Z&N$q`?-$?+> zb^M*lScK0U(WAF0L++h3BfKa79;3F>vV8n(x$36)gVHC|)!;9xZjXVBn##M8E+yfw zX}dO=z=!VCnG0_wT{83a1~H%16M&C>%3oJwuW!`M78b3^U!nauK+8taE{FG}(?`ViD_(V^jt)W~@vhpf$bmZCGsR-gEYZm?AgsWFTx zVOEXk5%uW%1vi1`%8~ShAq$WF;_hL*T(OZIV`mwcwA(L0OdEY`e!;JWUZhF1;zY>0 zc{*_popNflLJ;7V6yI)^sm|gCeHf_mAtS*~hoE`WN>bGQgs@!sPVK$pcs?7@lcN6S z5w2#ByP5HLY)iMNcS8APnW4;p#?rbFHs$DOI!T!13Wl(h`4EWL<`_I701*<8lyYSS2*1OZg1y=#+N6G9ApEe8AVmUn6 zanWsN76U8GQHa={6+Xo92>{g^vAW0V1pNW3CHN$iCqj2;Jb<+=|$n7KIQ4esSJ_iPyxS~;Gb}i zosP_UWa;6!)4Bs#Ht&))2DJ69S|;+@$Na35jCgw7-&Pyy;#|c(d@>Wf_|3(Q$*ZTk z&g+};J8#^s{O0=}tt?`E`l?z+LugDK#PTZ7d&2;M!kUP(o|E2N+WGjfL{vcA2$!ul z$!phHygY~9N>S&E+PFWcSQon+oW=sorc^TS1uLtGA;bFR8`VH8cnw~gvrnDZ$vW}0 z?!!Haf00Tr>B|FpiZr|Jmp)x$AfA5BoR;4yBHjY&JSo*{@X)KwYZ`BZ7@O?+#WHmY z3ULJIMZxRctzezgugi*q*Z`bPVJS_Ynkz?L5$4q6v(tS`qRum=G|k?PU)etdqe@lX z2Y&I(+^F$PV*!za6*Ij8e{0$b*!2pI`t_{JNIlEaWqRtl2E+OUjO*NFOBMoqYY0iXJ*3T-{Q1+q083AuOa=AQ3wFYbY3!ua&VCjSdk>y)iQYcI!$+D#*?&=!F6M< z22{|V^F@*dlN0E8vqCQIr~vo<6R2~mV=#LUo1P^nRHW9Dvg+7%Xt`?cs?(^_{N-fq zKyOF;vJo{-bN@Y0F-Zrp^D|zA9*6?3VXJBaNj_?I2bvkkyCt>?Jd#MC6L3Jh~`FBy5zml_{&W^QrqwA!) z96#AP~AK=`nfaI@q~G>6cf12M=7q?4PRh*8IuW&B1C$>CzkY@VqoB`3USVi^v#*th@9w?^;nrZA6PY4g zg`=1Beil{y9h+lTnq#YCMWRJV({E)bqSvxBC zqvy?=fBO`lt>fvPs>IW}6H7DCJ1tHsZrx2QB@Xsl_M84*$~UsPUW~$GVj*rG5N#6{ zhIFaO(p>AN%d_d(ij}21?dPHB4-(&flS*{&julQyN|O1wZwrg5TB!x7xPlI zK*tr#73XhmQF^VgC5(0yk`XI??^&5V0P9EgE|Ixkr4D6yNDz>Uq*2RG^68om+J9U%5pcCUEL( z)LhnL$BOo3Pf0*Eq#~*K-iacvMg>o)DPOE}Wd?ndqRNdwf|pt0lY*rVM`%gYpYL1; zUSzl-;%^i!$wdt2pNtrCTxVKNs>Z^e+^;9Zz|h~YVsHJ7X!?Q2MQz!1X5tZUJv?x4 z$`3%S1*oyjVw^-bQF;HxOsND8b+3Erc8CfD+f0IL1qv)HbcJKKF{2uJ6lT%u%YoCN zGYxgrqJtJ-=VbmNP(W;o^+R(Ek=S>amXk6zqu50Qtll<)v;ZpDQqkZR^czcCpi( zNd>$p$LC)v>B-^D1!X_zV$vP-qqsnPLR%;5Bs5&B4z3HG9t@NpT72;+QuF$?$ddVo zm-?C|V$c5$ZvT@rWi2hS*7sSYveDh1cPJ1}Tkj?ZUNQU9k-|Fv-@0yZ7@9iHW;h)hDtSh2 z=X+2WFv0d}y8701-E7|`qg{K=@jtD@WFtF)ztbArOmu;FJ0dEpS!;JiDLz@<58c<<-I zvh1NgT^@PwQ7WeGDB1!5F-VAmTh^xKU3Rtf4bn>bBgYyT2*ZX8iQLjgsKYUJwfeyZ z)GyREX)rW5qXdC&oHS+zIQAhHFVkSv(3PsqLQTa^=3RxKf}Ir$MY;fJ8=6*~7*8~f zel$NKSuo}0ABVFZZ(^UYGs>Mg0l0ce z8+l1Su3CAP92YNU10i1ohI2@Im>95rY_XwR-kZlRLVmt4WqQBNb|M*_6{r1=h)6@M#?rKy`cwvy{q;Mw4Fh{d1y zxAl!_yaOWlW}ACMZb8Vho_7Jc5I50m35maSk(fJjw6WJV)}@`5>)wid^*PEqiLu7O za9s*^)!H255rlb`#j;w6YC(@TY}?piL6}fC_rj6k!?EEqnHHnpK00OxG%>&Ge-0(Y zn6>X!{x(pKvnv3%ht$I^O6;Z2Nf0xgg$6Bk%!lVS97t!^KUz|6h;f$bOVi;Hv5;36 zL?-&b*me#(1im^K78@%CNx9^K7mTtsH+dx8m}8%TEe@mXe5nN%!bd9fP1Gw9-!PKj z?9=O()o7?=v7KfJEeC`EbGU6 zF2TSXof%JrDv)kMIG-OyPKUYxjieXTou!EP7w99wSv%2pzbYh*2qQF_cHSR_B?F9{ z2|&?=`29~0rNJE^y%QyzT+hBBV+3Tt`)tWwrDZi|bdhn5;}K!O9E9VxV2$8shB8uM zS?-1N9DvqL<+qNw01QWc<5@!pCy38w>w%>0(Ib$)Tc?=?!K2MR^ckf-!;c0qV#8vg z#bK5galCXzbOC#=Z39BJVE^Zz_^o#$(xEeB?aSOYrkymG?Q`6JJzZ~ucqLk6J9V%D ziin#`*em~7B)4Joru=F|&oJOq`k-+w}Xdw&R>9Ac-!xWdfL*zdSsQQ zSM|5};u;c&S9F&|1#FPU_Gq{-zsHHioJ@F~CrmVvR&O4BI>!m(W{gvS1-A(}{7L$F z+^0o0Dr$=kRT=j>j}8ea^YVDr)*czU;vPLD4Yz6UpOkQUl2lpJTOkxHrf9$Zy~WS- zBYg$_#HxuR=_90#_Rg&Cg>j-kd;1dqX7Yw@+Cg9YQpYfM>H^oM%E66i^}6#KTGt7e zHI`cNPWKkUB67U}%V2j38UBQ*?+ca>t$t8sPnk zu0#JMKp1_`;&w#=H1g`Hm?nExng&I=74fWXK^}$PXj;DBP?2k{l}BsCF9WlN_|uK7 zP_%^?z{Kd@ZTt7qL!99Al-dTd0Zff7Z^S6M*F0N9cCH|(DVug)wpNQ1yr)BvWoi6r ze(nH$j-V(Hse2Uru8@l;QE$qAWPf9bWv6KCo)g~C*g&V)C{cT_zCVf(!pBj>knG&! z2!>cJMXyLMx@&hs26BU1KdBC0)Bpeg literal 0 HcmV?d00001 diff --git a/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory.png b/macos/CursorAPI/Sources/CursorAPI/Resources/AgentIcons/factory.png new file mode 100644 index 0000000000000000000000000000000000000000..a410c21da9ccc6fdb8f35223183100dfe02666cd GIT binary patch literal 20812 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 AgentIntegrationStatus { + let url = factorySettingsURL() + guard fileManager.fileExists(atPath: url.path) else { + return AgentIntegrationStatus(id: .factory, installed: false, configPath: url.path, detail: "Factory settings not found") + } + let installed = factoryConfigMatches(settings: settings) + let detail = installed ? "Custom models installed" : providerStatusDetail(text: fileText(url), settings: settings) + return AgentIntegrationStatus(id: .factory, installed: installed, configPath: url.path, detail: detail) + } + + private func installFactory(settings: CursorAPISettings) throws { + let url = factorySettingsURL() + var root = try readJSONObject(url, defaultValue: [:]) + var models = (root["customModels"] as? [[String: Any]]) ?? [] + models.removeAll { item in + guard let id = item["id"] as? String else { return false } + return id.hasPrefix(Self.factoryModelIDPrefix) + } + for entry in factoryModelEntries(settings: settings) { + var entry = entry + entry["index"] = models.count + models.append(entry) + } + root["customModels"] = models + try writeJSONObject(root, to: url) + } + + private func factoryConfigMatches(settings: CursorAPISettings) -> Bool { + let url = factorySettingsURL() + guard fileManager.fileExists(atPath: url.path), + let root = try? readJSONObject(url, defaultValue: [:]), + let models = root["customModels"] as? [[String: Any]] else { + return false + } + return ComposerModels.all.allSatisfy { model in + models.contains { factoryModelEntryMatches($0, model: model, settings: settings) } + } + } + + private func factoryModelEntryMatches(_ entry: [String: Any], model: ComposerModel, settings: CursorAPISettings) -> Bool { + stringValue(entry["id"]) == "\(Self.factoryModelIDPrefix)\(model.id)" + && stringValue(entry["model"]) == model.id + && stringValue(entry["baseUrl"]) == settings.baseURL.absoluteString + && stringValue(entry["apiKey"]) == "cursor-local" + && stringValue(entry["provider"]) == "generic-chat-completion-api" + && stringValue(entry["displayName"]) == "\(CursorAPIBrand.displayName): \(model.name)" + && intValue(entry["maxOutputTokens"]) == model.outputLimit + && boolValue(entry["noImageSupport"]) == false + } + + private func factoryModelEntries(settings: CursorAPISettings) -> [[String: Any]] { + ComposerModels.all.map { model in + [ + "model": model.id, + "id": "\(Self.factoryModelIDPrefix)\(model.id)", + "baseUrl": settings.baseURL.absoluteString, + "apiKey": "cursor-local", + "displayName": "\(CursorAPIBrand.displayName): \(model.name)", + "maxOutputTokens": model.outputLimit, + "noImageSupport": false, + "provider": "generic-chat-completion-api" + ] + } + } + private func continueStatus(settings: CursorAPISettings) -> AgentIntegrationStatus { let url = continueConfigURL() guard fileManager.fileExists(atPath: url.path) else { @@ -725,6 +799,10 @@ public final class AgentProvisioner: @unchecked Sendable { homeDirectory.appending(path: ".pi/agent/models.json") } + private func factorySettingsURL() -> URL { + homeDirectory.appending(path: ".factory/settings.json") + } + private func continueConfigURL() -> URL { homeDirectory.appending(path: ".continue/config.yaml") } diff --git a/macos/CursorAPI/Sources/CursorAPICore/Models.swift b/macos/CursorAPI/Sources/CursorAPICore/Models.swift index e396ff5..71cd1b4 100644 --- a/macos/CursorAPI/Sources/CursorAPICore/Models.swift +++ b/macos/CursorAPI/Sources/CursorAPICore/Models.swift @@ -252,6 +252,7 @@ public enum AgentIntegrationID: String, CaseIterable, Codable, Sendable { case cline case kilo case pi + case factory case continueDev = "continue" case aider case roo @@ -270,6 +271,8 @@ public enum AgentIntegrationID: String, CaseIterable, Codable, Sendable { return "Kilo Code" case .pi: return "pi" + case .factory: + return "Factory" case .continueDev: return "Continue" case .aider: diff --git a/macos/CursorAPI/Tests/CursorAPITests/AgentProvisionerTests.swift b/macos/CursorAPI/Tests/CursorAPITests/AgentProvisionerTests.swift index 1b83501..077685d 100644 --- a/macos/CursorAPI/Tests/CursorAPITests/AgentProvisionerTests.swift +++ b/macos/CursorAPI/Tests/CursorAPITests/AgentProvisionerTests.swift @@ -197,6 +197,72 @@ final class AgentProvisionerTests: XCTestCase { XCTAssertTrue(provisioner.status(for: .pi, settings: settings).installed) } + func testInstallsFactoryCustomModels() throws { + let home = try temporaryHome() + let provisioner = AgentProvisioner(homeDirectory: home) + let settings = CursorAPISettings(port: 8787) + try provisioner.install(.factory, settings: settings) + + let config = home.appending(path: ".factory/settings.json") + let root = try readJSONObject(config) + let models = try XCTUnwrap(root["customModels"] as? [[String: Any]]) + XCTAssertEqual(models.count, 2) + + let primary = try XCTUnwrap(models.first { $0["id"] as? String == "custom:cursorapi:composer-2.5" }) + XCTAssertEqual(primary["model"] as? String, "composer-2.5") + XCTAssertEqual(primary["baseUrl"] as? String, "http://127.0.0.1:8787/v1") + XCTAssertEqual(primary["apiKey"] as? String, "cursor-local") + XCTAssertEqual(primary["provider"] as? String, "generic-chat-completion-api") + XCTAssertEqual(primary["displayName"] as? String, "\(CursorAPIBrand.displayName): Composer 2.5") + XCTAssertEqual((primary["maxOutputTokens"] as? NSNumber)?.intValue, 65_536) + XCTAssertEqual(primary["noImageSupport"] as? Bool, false) + XCTAssertEqual((primary["index"] as? NSNumber)?.intValue, 0) + + let fast = try XCTUnwrap(models.first { $0["id"] as? String == "custom:cursorapi:composer-2.5-fast" }) + XCTAssertEqual(fast["model"] as? String, "composer-2.5-fast") + XCTAssertEqual(fast["displayName"] as? String, "\(CursorAPIBrand.displayName): Composer 2.5 Fast") + XCTAssertEqual((fast["index"] as? NSNumber)?.intValue, 1) + + XCTAssertTrue(provisioner.status(for: .factory, settings: settings).installed) + } + + func testInstallFactoryPreservesExistingEntriesAndAvoidsDuplicates() throws { + let home = try temporaryHome() + let provisioner = AgentProvisioner(homeDirectory: home) + let settings = CursorAPISettings(port: 8787) + let config = home.appending(path: ".factory/settings.json") + try FileManager.default.createDirectory(at: config.deletingLastPathComponent(), withIntermediateDirectories: true) + try """ + { + "customModels": [ + { + "model": "my-other-model", + "id": "custom:other:model", + "index": 0, + "baseUrl": "http://localhost:1234/v1", + "apiKey": "other", + "displayName": "Other Model", + "maxOutputTokens": 4096, + "noImageSupport": false, + "provider": "openai" + } + ], + "someOtherSetting": true + } + """.write(to: config, atomically: true, encoding: .utf8) + + try provisioner.install(.factory, settings: settings) + try provisioner.install(.factory, settings: settings) + + let root = try readJSONObject(config) + XCTAssertEqual(root["someOtherSetting"] as? Bool, true) + let models = try XCTUnwrap(root["customModels"] as? [[String: Any]]) + XCTAssertEqual(models.count, 3, "Existing entry plus the two Composer entries, with no duplicates after re-install") + XCTAssertEqual(models.filter { ($0["id"] as? String)?.hasPrefix("custom:cursorapi:") == true }.count, 2) + XCTAssertNotNil(models.first { $0["id"] as? String == "custom:other:model" }) + XCTAssertTrue(provisioner.status(for: .factory, settings: settings).installed) + } + func testInstallsContinueModelsInExistingConfig() throws { let home = try temporaryHome() let provisioner = AgentProvisioner(homeDirectory: home) @@ -606,6 +672,7 @@ final class AgentProvisionerTests: XCTestCase { try provisioner.install(.continueDev, settings: original) try provisioner.install(.aider, settings: original) try provisioner.install(.roo, settings: original) + try provisioner.install(.factory, settings: original) for id in AgentIntegrationID.allCases { let status = provisioner.status(for: id, settings: moved) diff --git a/public/factory-logo.svg b/public/factory-logo.svg new file mode 100644 index 0000000..25d051a --- /dev/null +++ b/public/factory-logo.svg @@ -0,0 +1 @@ +Factory.ai diff --git a/public/setup.md b/public/setup.md index ab29c32..61a73e3 100644 --- a/public/setup.md +++ b/public/setup.md @@ -1,6 +1,6 @@ # API for Cursor -API for Cursor is now distributed as a local macOS app. The local app starts an OpenAI-compatible server on your machine, stores your Cursor API key locally, and configures agent tools like OpenCode, Codex, VS Code, Cline, Kilo Code, Continue, Aider, and Roo. +API for Cursor is now distributed as a local macOS app. The local app starts an OpenAI-compatible server on your machine, stores your Cursor API key locally, and configures agent tools like OpenCode, Codex, VS Code, Cline, Kilo Code, Factory, Continue, Aider, and Roo. Download the latest DMG: @@ -48,6 +48,44 @@ model = "composer-2.5" model_provider = "cursor-composer" ``` +## Factory + +Use the app's Factory installer from **Agent Setup**. It adds Composer models as +Factory.ai Droid custom models in `~/.factory/settings.json` (the file is backed +up first), or configure them manually by adding entries to the `customModels` +array: + +```json +{ + "customModels": [ + { + "model": "composer-2.5", + "id": "custom:cursorapi:composer-2.5", + "baseUrl": "http://127.0.0.1:8787/v1", + "apiKey": "cursor-local", + "displayName": "API for Cursor: Composer 2.5", + "maxOutputTokens": 65536, + "noImageSupport": false, + "provider": "generic-chat-completion-api" + }, + { + "model": "composer-2.5-fast", + "id": "custom:cursorapi:composer-2.5-fast", + "baseUrl": "http://127.0.0.1:8787/v1", + "apiKey": "cursor-local", + "displayName": "API for Cursor: Composer 2.5 Fast", + "maxOutputTokens": 65536, + "noImageSupport": false, + "provider": "generic-chat-completion-api" + } + ] +} +``` + +Restart Factory or open a new session to see the models in the picker. The +one-click Factory flow is adapted from +[DroidProxy](https://github.com/anand-92/droidproxy). + ## Hosted Endpoint Status Cursor asked us to take down the hosted API endpoint. The old hosted routes remain online temporarily while the local app is verified, but the production path is the local app.