diff --git a/Cargo.lock b/Cargo.lock index d645d19..7ebab94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "async-openai" version = "0.38.2" @@ -21,7 +39,7 @@ dependencies = [ "eventsource-stream", "futures", "getrandom 0.3.4", - "rand", + "rand 0.9.4", "reqwest 0.13.3", "secrecy", "serde", @@ -64,6 +82,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "aws-lc-rs" version = "1.17.0" @@ -141,18 +165,39 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -165,6 +210,39 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.2.62" @@ -189,6 +267,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "cmake" version = "0.1.58" @@ -208,6 +300,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -234,6 +332,64 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -269,6 +425,26 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_builder" version = "0.20.2" @@ -300,6 +476,18 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -317,6 +505,65 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -359,6 +606,22 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -495,6 +758,17 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -550,6 +824,17 @@ dependencies = [ "url", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.14" @@ -590,6 +875,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.4.0" @@ -666,12 +969,27 @@ dependencies = [ "http", "hyper", "hyper-util", + "log", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -713,6 +1031,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -923,11 +1265,38 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "10.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +dependencies = [ + "base64", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "pem", + "rand 0.8.6", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", + "simple_asn1", + "zeroize", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "leb128fmt" @@ -954,6 +1323,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libz-sys" version = "1.1.29" @@ -1080,6 +1455,109 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "octocrab" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce7ace5d83b077dd50ff01214a81feea17e258b8f677590c2286add76dc8238e" +dependencies = [ + "arc-swap", + "async-trait", + "base64", + "bytes", + "cargo_metadata", + "cfg-if", + "chrono", + "futures", + "futures-util", + "getrandom 0.2.17", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", + "web-time", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1145,6 +1623,30 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1168,18 +1670,78 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -1195,6 +1757,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1214,6 +1782,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1241,9 +1818,14 @@ dependencies = [ name = "pudoxd" version = "0.1.0" dependencies = [ + "anyhow", "axum", "git2", + "jsonwebtoken", + "octocrab", "reqwest 0.12.28", + "rustls", + "secrecy", "serde", "serde_json", "tokio", @@ -1282,7 +1864,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -1329,14 +1911,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -1346,7 +1949,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -1452,6 +2064,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1466,6 +2088,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -1501,7 +2143,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -1605,6 +2249,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.10.3" @@ -1643,6 +2301,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -1710,6 +2372,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1735,6 +2408,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd_cesu8" version = "1.1.1" @@ -1751,6 +2434,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -1763,6 +2458,27 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.6.3" @@ -1773,6 +2489,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1885,6 +2617,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -2015,6 +2778,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", "url", ] @@ -2094,6 +2858,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicase" version = "2.9.0" @@ -2128,6 +2898,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2148,6 +2919,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -2323,6 +3100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", + "serde", "wasm-bindgen", ] @@ -2344,6 +3122,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -2630,6 +3443,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/bin/pudoxd/Cargo.toml b/bin/pudoxd/Cargo.toml index 25fa47e..9465531 100644 --- a/bin/pudoxd/Cargo.toml +++ b/bin/pudoxd/Cargo.toml @@ -17,3 +17,8 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" git2 = { version = "0.20", default-features = false, features = ["vendored-libgit2", "vendored-openssl", "https"] } tokio-stream = { version = "0.1", features = ["sync"] } +octocrab = "0.50.0" +jsonwebtoken = "10.3.0" +anyhow = "1.0.102" +secrecy = "0.10" +rustls = { version = "0.23", features = ["aws-lc-rs"], default-features = false } diff --git a/bin/pudoxd/src/github.rs b/bin/pudoxd/src/github.rs new file mode 100644 index 0000000..677d8e5 --- /dev/null +++ b/bin/pudoxd/src/github.rs @@ -0,0 +1,130 @@ +use anyhow::{Context, Result}; +use jsonwebtoken::EncodingKey; +use octocrab::{models::AppId, Octocrab}; +use secrecy::ExposeSecret; + +#[derive(Clone)] +pub struct GitHub { + octocrab: Octocrab, +} + +impl GitHub { + pub async fn new() -> Result { + let app_id_str = std::env::var("GITHUB_APP_ID").context("GITHUB_APP_ID must be set")?; + let private_key = std::env::var("GITHUB_APP_PRIVATE_KEY") + .context("GITHUB_APP_PRIVATE_KEY must be set")?; + let app_id: u64 = app_id_str.parse().context("GITHUB_APP_ID must be a number")?; + let formatted_key = private_key.replace("\\n", "\n"); + let key = EncodingKey::from_rsa_pem(formatted_key.as_bytes()) + .context("Failed to parse GITHUB_APP_PRIVATE_KEY as RSA PEM")?; + let octocrab = Octocrab::builder() + .app(AppId(app_id), key) + .build() + .context("Failed to build octocrab client")?; + Ok(GitHub { octocrab }) + } + + // Returns an installation client scoped to the installation that covers `owner/repo`. + async fn installation_client_for_repo(&self, owner: &str, repo: &str) -> Result { + let installation = self + .octocrab + .apps() + .get_repository_installation(owner, repo) + .await + .with_context(|| { + format!( + "GitHub App is not installed on {}/{} (or has no access to it)", + owner, repo + ) + })?; + self.octocrab + .installation(installation.id) + .map_err(|e| anyhow::anyhow!("Failed to create installation client: {}", e)) + } + + // Returns a short-lived installation access token for the installation that covers + // `owner/repo`. Use this for git2 credentials when cloning or pushing. + pub async fn installation_token_for_repo(&self, owner: &str, repo: &str) -> Result { + let client = self.installation_client_for_repo(owner, repo).await?; + let secret = client + .installation_token() + .await + .context("Failed to acquire installation access token")?; + Ok(secret.expose_secret().to_string()) + } + + // Creates a PR for `branch` or updates the existing one if the branch already has an open PR. + // Returns the PR HTML URL on success. + pub async fn upsert_pull_request( + &self, + owner: &str, + repo: &str, + branch: &str, + title: &str, + body: Option<&str>, + ) -> Option { + let client = match self.installation_client_for_repo(owner, repo).await { + Ok(c) => c, + Err(e) => { + tracing::error!("Failed to get installation client: {}", e); + return None; + } + }; + + let handler = client.pulls(owner, repo); + let builder = handler.create(title, branch, "main"); + let builder = if let Some(b) = body { builder.body(b) } else { builder }; + match builder.send().await { + Ok(pr) => { + let url = pr.html_url.to_string(); + tracing::info!("Pull request opened: {}", url); + return Some(url); + } + Err(ref e) if format!("{e}").contains("already exists") => { + // Fall through to find-and-update + } + Err(e) => { + tracing::error!("GitHub PR creation failed: {}", e); + return None; + } + } + + tracing::info!("PR already exists for branch {}, updating instead", branch); + let prs = match client + .pulls(owner, repo) + .list() + .head(format!("{}:{}", owner, branch)) + .state(octocrab::params::State::Open) + .send() + .await + { + Ok(p) => p, + Err(e) => { + tracing::error!("Failed to list PRs: {}", e); + return None; + } + }; + let pr_number = match prs.items.first() { + Some(p) => p.number, + None => { + tracing::error!("No open PR found for branch {}", branch); + return None; + } + }; + + let handler = client.pulls(owner, repo); + let update = handler.update(pr_number).title(title); + let update = if let Some(b) = body { update.body(b) } else { update }; + match update.send().await { + Ok(pr) => { + let url = pr.html_url.to_string(); + tracing::info!("Pull request updated: {}", url); + Some(url) + } + Err(e) => { + tracing::error!("GitHub PR update failed: {}", e); + None + } + } + } +} diff --git a/bin/pudoxd/src/main.rs b/bin/pudoxd/src/main.rs index 965b081..56d945b 100644 --- a/bin/pudoxd/src/main.rs +++ b/bin/pudoxd/src/main.rs @@ -16,27 +16,39 @@ use tokio::sync::{mpsc, RwLock}; use tokio_stream::wrappers::BroadcastStream; use tokio_stream::StreamExt as _; +mod github; mod linear; mod session; +use crate::github::GitHub; use crate::linear::{Linear, WebhookPayload}; -use crate::session::{run_session, Session, SessionMessage}; +use crate::session::{parse_github_owner_repo, run_session, Session, SessionMessage}; #[derive(Clone)] struct App { linear: Linear, + github: GitHub, sessions: Arc>>, http: reqwest::Client, } #[tokio::main] async fn main() -> Result<(), Box> { + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Failed to install rustls crypto provider"); + tracing_subscriber::fmt::init(); tracing::info!("Starting pudoxd..."); + let github = GitHub::new() + .await + .map_err(|e| format!("GitHub init failed: {}", e))?; + let app = App { linear: Linear::new(), + github, sessions: Arc::new(RwLock::new(HashMap::new())), http: reqwest::Client::new(), }; @@ -163,6 +175,7 @@ async fn task_result_handler( tokio::spawn(handle_task_result( app.linear.clone(), + app.github.clone(), session_id, dir, issue_id, @@ -198,143 +211,9 @@ async fn task_history_handler( } } -async fn upsert_pull_request( - client: &reqwest::Client, - token: &str, - repo_slug: &str, - branch: &str, - title: &str, - body: Option<&str>, -) -> Option { - let auth = format!("Bearer {}", token); - let base_url = format!("https://api.github.com/repos/{}/pulls", repo_slug); - - // Try to create the PR first. - let mut create_body = serde_json::json!({ - "title": title, - "head": branch, - "base": "main", - }); - if let Some(b) = body { - create_body["body"] = serde_json::Value::String(b.to_string()); - } - - let create_resp = match client - .post(&base_url) - .header("Authorization", &auth) - .header("User-Agent", "pudox/0.1") - .header("Accept", "application/vnd.github+json") - .json(&create_body) - .send() - .await - { - Ok(r) => r, - Err(e) => { - tracing::error!("GitHub API request failed: {}", e); - return None; - } - }; - - if create_resp.status().is_success() { - return match create_resp.json::().await { - Ok(json) => { - let url = json["html_url"].as_str().unwrap_or("").to_string(); - tracing::info!("Pull request opened: {}", url); - Some(url) - } - Err(e) => { - tracing::error!("Failed to parse PR create response: {}", e); - None - } - }; - } - - // 422 means the PR already exists — fetch it and update instead. - if create_resp.status().as_u16() != 422 { - let status = create_resp.status(); - let text = create_resp.text().await.unwrap_or_default(); - tracing::error!("GitHub PR creation failed {}: {}", status, text); - return None; - } - - tracing::info!("PR already exists for branch {}, updating instead", branch); - - // Find the existing PR by head branch. - let list_url = format!("{}?head={}&state=open", base_url, branch); - let list_resp = match client - .get(&list_url) - .header("Authorization", &auth) - .header("User-Agent", "pudox/0.1") - .header("Accept", "application/vnd.github+json") - .send() - .await - { - Ok(r) => r, - Err(e) => { - tracing::error!("GitHub API list PRs failed: {}", e); - return None; - } - }; - - let prs: serde_json::Value = match list_resp.json().await { - Ok(v) => v, - Err(e) => { - tracing::error!("Failed to parse PR list response: {}", e); - return None; - } - }; - - let pr_number = match prs[0]["number"].as_u64() { - Some(n) => n, - None => { - tracing::error!("No open PR found for branch {}", branch); - return None; - } - }; - - let patch_url = format!("https://api.github.com/repos/{}/pulls/{}", repo_slug, pr_number); - let mut patch_body = serde_json::json!({ "title": title }); - if let Some(b) = body { - patch_body["body"] = serde_json::Value::String(b.to_string()); - } - - match client - .patch(&patch_url) - .header("Authorization", &auth) - .header("User-Agent", "pudox/0.1") - .header("Accept", "application/vnd.github+json") - .json(&patch_body) - .send() - .await - { - Ok(resp) if resp.status().is_success() => { - match resp.json::().await { - Ok(json) => { - let url = json["html_url"].as_str().unwrap_or("").to_string(); - tracing::info!("Pull request updated: {}", url); - Some(url) - } - Err(e) => { - tracing::error!("Failed to parse PR update response: {}", e); - None - } - } - } - Ok(resp) => { - let status = resp.status(); - let text = resp.text().await.unwrap_or_default(); - tracing::error!("GitHub PR update failed {}: {}", status, text); - None - } - Err(e) => { - tracing::error!("GitHub API PATCH failed: {}", e); - None - } - } -} - async fn handle_task_result( linear: crate::linear::Linear, + github: GitHub, session_id: String, dir: std::path::PathBuf, issue_id: Option, @@ -352,6 +231,33 @@ async fn handle_task_result( let dir_clone = dir.clone(); let msg_clone = commit_message.clone(); + // Read the origin URL from the local repo so we can look up the right installation. + let remote_url = { + let dir_ref = dir.clone(); + tokio::task::spawn_blocking(move || { + let Ok(repo) = git2::Repository::open(&dir_ref) else { + return String::new(); + }; + let Ok(remote) = repo.find_remote("origin") else { + return String::new(); + }; + remote.url().unwrap_or("").to_string() + }) + .await + .unwrap_or_default() + }; + let (owner, repo_name) = parse_github_owner_repo(&remote_url); + let git_token = match github + .installation_token_for_repo(&owner, &repo_name) + .await + { + Ok(t) => t, + Err(e) => { + tracing::error!("Failed to get installation token for push: {}", e); + return; + } + }; + let commit_result = tokio::task::spawn_blocking(move || -> Result { let repo = git2::Repository::open(&dir_clone)?; @@ -371,8 +277,8 @@ async fn handle_task_result( let head_commit = repo.head()?.peel_to_commit()?; repo.branch(&branch_name_clone, &head_commit, true)?; - // Push branch to origin with GITHUB_TOKEN credentials - let token = std::env::var("GITHUB_TOKEN").unwrap_or_default(); + // Push branch to origin using GitHub App installation token + let token = git_token; let mut remote = repo.find_remote("origin")?; let refspec = format!( "refs/heads/{name}:refs/heads/{name}", @@ -422,25 +328,27 @@ async fn handle_task_result( .trim_start_matches(['/', ':']) .to_string(); - let github_token = std::env::var("GITHUB_TOKEN").unwrap_or_default(); - let pr_url = if repo_slug.is_empty() || github_token.is_empty() { - tracing::warn!( - "Skipping PR upsert: repo_slug={:?}, token_present={}", - repo_slug, - !github_token.is_empty() - ); + let pr_url = if repo_slug.is_empty() { + tracing::warn!("Skipping PR upsert: could not parse repo slug from remote URL"); None } else { - let client = reqwest::Client::new(); - upsert_pull_request( - &client, - &github_token, - &repo_slug, - &branch_name, - &payload.commit_title, - payload.pull_request_description.as_deref(), - ) - .await + let mut parts = repo_slug.splitn(2, '/'); + let owner = parts.next().unwrap_or(""); + let repo = parts.next().unwrap_or(""); + if owner.is_empty() || repo.is_empty() { + tracing::warn!("Skipping PR upsert: malformed repo slug {:?}", repo_slug); + None + } else { + github + .upsert_pull_request( + owner, + repo, + &branch_name, + &payload.commit_title, + payload.pull_request_description.as_deref(), + ) + .await + } }; // ── 3. Link PR to Linear issue ───────────────────────────────────────────── @@ -645,6 +553,7 @@ async fn webhook_handler(State(app): State, body: String) -> &'static str { rx, session_id.clone(), app.linear.clone(), + app.github.clone(), sse_tx.clone(), pending_prompts.clone(), )); diff --git a/bin/pudoxd/src/session.rs b/bin/pudoxd/src/session.rs index a70f9dd..9bb1c4b 100644 --- a/bin/pudoxd/src/session.rs +++ b/bin/pudoxd/src/session.rs @@ -3,8 +3,24 @@ use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{mpsc, RwLock}; +use crate::github::GitHub; use crate::linear::{Linear, WebhookPayload}; +// Extracts ("owner", "repo") from HTTPS or SSH GitHub URLs. +// Returns ("", "") if the URL doesn't look like a GitHub URL. +pub(crate) fn parse_github_owner_repo(url: &str) -> (String, String) { + let slug = url + .trim_end_matches(".git") + .rsplit("github.com") + .next() + .unwrap_or("") + .trim_start_matches(['/', ':']); + let mut parts = slug.splitn(2, '/'); + let owner = parts.next().unwrap_or("").to_string(); + let repo = parts.next().unwrap_or("").to_string(); + (owner, repo) +} + pub enum SessionMessage { Init(WebhookPayload), SandboxReady(SandboxContext), @@ -78,6 +94,7 @@ async fn init( session_id: String, prompt_context: Option, linear: Linear, + github: GitHub, ) { let repo_url = match prompt_context { Some(ref ctx) => match resolve_git_url(ctx).await { @@ -153,7 +170,20 @@ async fn init( } else { let dir_path = dir.clone(); let url = repo_url.clone(); - let token = std::env::var("GITHUB_TOKEN").unwrap_or_default(); + // Parse owner/repo from the URL to look up the right installation. + let (owner, repo_name) = parse_github_owner_repo(&url); + let token = match github.installation_token_for_repo(&owner, &repo_name).await { + Ok(t) => t, + Err(e) => { + tracing::error!( + "Failed to get installation token for {}/{}: {}", + owner, + repo_name, + e + ); + return; + } + }; let clone_result = tokio::task::spawn_blocking(move || { let mut callbacks = git2::RemoteCallbacks::new(); callbacks.credentials(move |_url, _username, _allowed| { @@ -273,6 +303,7 @@ pub async fn run_session( mut rx: mpsc::Receiver, session_id: String, linear: Linear, + github: GitHub, sse_tx: tokio::sync::broadcast::Sender, pending_prompts: Arc>>, ) { @@ -284,7 +315,13 @@ pub async fn run_session( match msg { SessionMessage::Init(payload) => { let ctx = payload.prompt_context.clone(); - tokio::spawn(init(tx.clone(), session_id.clone(), ctx, linear.clone())); + tokio::spawn(init( + tx.clone(), + session_id.clone(), + ctx, + linear.clone(), + github.clone(), + )); } SessionMessage::SandboxReady(ctx) => {