diff options
| -rw-r--r-- | package-lock.json | 362 | ||||
| -rw-r--r-- | package.json | 5 | ||||
| -rw-r--r-- | src/main/claude/index.ts | 142 | ||||
| -rw-r--r-- | src/main/claude/phases.ts | 104 | ||||
| -rw-r--r-- | src/main/db/index.ts | 31 | ||||
| -rw-r--r-- | src/main/db/projects.ts | 38 | ||||
| -rw-r--r-- | src/main/db/schema.ts | 35 | ||||
| -rw-r--r-- | src/main/db/sessions.ts | 106 |
8 files changed, 821 insertions, 2 deletions
diff --git a/package-lock.json b/package-lock.json index 74bac1f..64d46a9 100644 --- a/package-lock.json +++ b/package-lock.json | |||
| @@ -9,9 +9,11 @@ | |||
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "dependencies": { | 11 | "dependencies": { |
| 12 | "@anthropic-ai/claude-agent-sdk": "^0.2.63", | ||
| 12 | "better-sqlite3": "12.2.0", | 13 | "better-sqlite3": "12.2.0", |
| 13 | "react": "^19.1.1", | 14 | "react": "^19.1.1", |
| 14 | "react-dom": "^19.1.1" | 15 | "react-dom": "^19.1.1", |
| 16 | "uuid": "^13.0.0" | ||
| 15 | }, | 17 | }, |
| 16 | "devDependencies": { | 18 | "devDependencies": { |
| 17 | "@electron/rebuild": "4.0.1", | 19 | "@electron/rebuild": "4.0.1", |
| @@ -19,6 +21,7 @@ | |||
| 19 | "@types/node": "24.3.2", | 21 | "@types/node": "24.3.2", |
| 20 | "@types/react": "^19.1.13", | 22 | "@types/react": "^19.1.13", |
| 21 | "@types/react-dom": "^19.1.9", | 23 | "@types/react-dom": "^19.1.9", |
| 24 | "@types/uuid": "^10.0.0", | ||
| 22 | "@vitejs/plugin-react": "^5.0.2", | 25 | "@vitejs/plugin-react": "^5.0.2", |
| 23 | "concurrently": "^9.2.1", | 26 | "concurrently": "^9.2.1", |
| 24 | "electron": "38.1.0", | 27 | "electron": "38.1.0", |
| @@ -28,6 +31,29 @@ | |||
| 28 | "wait-on": "^8.0.5" | 31 | "wait-on": "^8.0.5" |
| 29 | } | 32 | } |
| 30 | }, | 33 | }, |
| 34 | "node_modules/@anthropic-ai/claude-agent-sdk": { | ||
| 35 | "version": "0.2.63", | ||
| 36 | "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.63.tgz", | ||
| 37 | "integrity": "sha512-ZNiaQb/v6xkbrGt3dtq5J0DGY+AaOhoehUyposa3msvlAlkTHWNGR+NhbCcTE0ML1U91xhPqMAAwZIUqrlkKyQ==", | ||
| 38 | "license": "SEE LICENSE IN README.md", | ||
| 39 | "engines": { | ||
| 40 | "node": ">=18.0.0" | ||
| 41 | }, | ||
| 42 | "optionalDependencies": { | ||
| 43 | "@img/sharp-darwin-arm64": "^0.34.2", | ||
| 44 | "@img/sharp-darwin-x64": "^0.34.2", | ||
| 45 | "@img/sharp-linux-arm": "^0.34.2", | ||
| 46 | "@img/sharp-linux-arm64": "^0.34.2", | ||
| 47 | "@img/sharp-linux-x64": "^0.34.2", | ||
| 48 | "@img/sharp-linuxmusl-arm64": "^0.34.2", | ||
| 49 | "@img/sharp-linuxmusl-x64": "^0.34.2", | ||
| 50 | "@img/sharp-win32-arm64": "^0.34.2", | ||
| 51 | "@img/sharp-win32-x64": "^0.34.2" | ||
| 52 | }, | ||
| 53 | "peerDependencies": { | ||
| 54 | "zod": "^4.0.0" | ||
| 55 | } | ||
| 56 | }, | ||
| 31 | "node_modules/@babel/code-frame": { | 57 | "node_modules/@babel/code-frame": { |
| 32 | "version": "7.27.1", | 58 | "version": "7.27.1", |
| 33 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", | 59 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", |
| @@ -1749,6 +1775,310 @@ | |||
| 1749 | "@hapi/hoek": "^11.0.2" | 1775 | "@hapi/hoek": "^11.0.2" |
| 1750 | } | 1776 | } |
| 1751 | }, | 1777 | }, |
| 1778 | "node_modules/@img/sharp-darwin-arm64": { | ||
| 1779 | "version": "0.34.5", | ||
| 1780 | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", | ||
| 1781 | "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", | ||
| 1782 | "cpu": [ | ||
| 1783 | "arm64" | ||
| 1784 | ], | ||
| 1785 | "license": "Apache-2.0", | ||
| 1786 | "optional": true, | ||
| 1787 | "os": [ | ||
| 1788 | "darwin" | ||
| 1789 | ], | ||
| 1790 | "engines": { | ||
| 1791 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 1792 | }, | ||
| 1793 | "funding": { | ||
| 1794 | "url": "https://opencollective.com/libvips" | ||
| 1795 | }, | ||
| 1796 | "optionalDependencies": { | ||
| 1797 | "@img/sharp-libvips-darwin-arm64": "1.2.4" | ||
| 1798 | } | ||
| 1799 | }, | ||
| 1800 | "node_modules/@img/sharp-darwin-x64": { | ||
| 1801 | "version": "0.34.5", | ||
| 1802 | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", | ||
| 1803 | "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", | ||
| 1804 | "cpu": [ | ||
| 1805 | "x64" | ||
| 1806 | ], | ||
| 1807 | "license": "Apache-2.0", | ||
| 1808 | "optional": true, | ||
| 1809 | "os": [ | ||
| 1810 | "darwin" | ||
| 1811 | ], | ||
| 1812 | "engines": { | ||
| 1813 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 1814 | }, | ||
| 1815 | "funding": { | ||
| 1816 | "url": "https://opencollective.com/libvips" | ||
| 1817 | }, | ||
| 1818 | "optionalDependencies": { | ||
| 1819 | "@img/sharp-libvips-darwin-x64": "1.2.4" | ||
| 1820 | } | ||
| 1821 | }, | ||
| 1822 | "node_modules/@img/sharp-libvips-darwin-arm64": { | ||
| 1823 | "version": "1.2.4", | ||
| 1824 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", | ||
| 1825 | "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", | ||
| 1826 | "cpu": [ | ||
| 1827 | "arm64" | ||
| 1828 | ], | ||
| 1829 | "license": "LGPL-3.0-or-later", | ||
| 1830 | "optional": true, | ||
| 1831 | "os": [ | ||
| 1832 | "darwin" | ||
| 1833 | ], | ||
| 1834 | "funding": { | ||
| 1835 | "url": "https://opencollective.com/libvips" | ||
| 1836 | } | ||
| 1837 | }, | ||
| 1838 | "node_modules/@img/sharp-libvips-darwin-x64": { | ||
| 1839 | "version": "1.2.4", | ||
| 1840 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", | ||
| 1841 | "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", | ||
| 1842 | "cpu": [ | ||
| 1843 | "x64" | ||
| 1844 | ], | ||
| 1845 | "license": "LGPL-3.0-or-later", | ||
| 1846 | "optional": true, | ||
| 1847 | "os": [ | ||
| 1848 | "darwin" | ||
| 1849 | ], | ||
| 1850 | "funding": { | ||
| 1851 | "url": "https://opencollective.com/libvips" | ||
| 1852 | } | ||
| 1853 | }, | ||
| 1854 | "node_modules/@img/sharp-libvips-linux-arm": { | ||
| 1855 | "version": "1.2.4", | ||
| 1856 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", | ||
| 1857 | "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", | ||
| 1858 | "cpu": [ | ||
| 1859 | "arm" | ||
| 1860 | ], | ||
| 1861 | "license": "LGPL-3.0-or-later", | ||
| 1862 | "optional": true, | ||
| 1863 | "os": [ | ||
| 1864 | "linux" | ||
| 1865 | ], | ||
| 1866 | "funding": { | ||
| 1867 | "url": "https://opencollective.com/libvips" | ||
| 1868 | } | ||
| 1869 | }, | ||
| 1870 | "node_modules/@img/sharp-libvips-linux-arm64": { | ||
| 1871 | "version": "1.2.4", | ||
| 1872 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", | ||
| 1873 | "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", | ||
| 1874 | "cpu": [ | ||
| 1875 | "arm64" | ||
| 1876 | ], | ||
| 1877 | "license": "LGPL-3.0-or-later", | ||
| 1878 | "optional": true, | ||
| 1879 | "os": [ | ||
| 1880 | "linux" | ||
| 1881 | ], | ||
| 1882 | "funding": { | ||
| 1883 | "url": "https://opencollective.com/libvips" | ||
| 1884 | } | ||
| 1885 | }, | ||
| 1886 | "node_modules/@img/sharp-libvips-linux-x64": { | ||
| 1887 | "version": "1.2.4", | ||
| 1888 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", | ||
| 1889 | "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", | ||
| 1890 | "cpu": [ | ||
| 1891 | "x64" | ||
| 1892 | ], | ||
| 1893 | "license": "LGPL-3.0-or-later", | ||
| 1894 | "optional": true, | ||
| 1895 | "os": [ | ||
| 1896 | "linux" | ||
| 1897 | ], | ||
| 1898 | "funding": { | ||
| 1899 | "url": "https://opencollective.com/libvips" | ||
| 1900 | } | ||
| 1901 | }, | ||
| 1902 | "node_modules/@img/sharp-libvips-linuxmusl-arm64": { | ||
| 1903 | "version": "1.2.4", | ||
| 1904 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", | ||
| 1905 | "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", | ||
| 1906 | "cpu": [ | ||
| 1907 | "arm64" | ||
| 1908 | ], | ||
| 1909 | "license": "LGPL-3.0-or-later", | ||
| 1910 | "optional": true, | ||
| 1911 | "os": [ | ||
| 1912 | "linux" | ||
| 1913 | ], | ||
| 1914 | "funding": { | ||
| 1915 | "url": "https://opencollective.com/libvips" | ||
| 1916 | } | ||
| 1917 | }, | ||
| 1918 | "node_modules/@img/sharp-libvips-linuxmusl-x64": { | ||
| 1919 | "version": "1.2.4", | ||
| 1920 | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", | ||
| 1921 | "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", | ||
| 1922 | "cpu": [ | ||
| 1923 | "x64" | ||
| 1924 | ], | ||
| 1925 | "license": "LGPL-3.0-or-later", | ||
| 1926 | "optional": true, | ||
| 1927 | "os": [ | ||
| 1928 | "linux" | ||
| 1929 | ], | ||
| 1930 | "funding": { | ||
| 1931 | "url": "https://opencollective.com/libvips" | ||
| 1932 | } | ||
| 1933 | }, | ||
| 1934 | "node_modules/@img/sharp-linux-arm": { | ||
| 1935 | "version": "0.34.5", | ||
| 1936 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", | ||
| 1937 | "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", | ||
| 1938 | "cpu": [ | ||
| 1939 | "arm" | ||
| 1940 | ], | ||
| 1941 | "license": "Apache-2.0", | ||
| 1942 | "optional": true, | ||
| 1943 | "os": [ | ||
| 1944 | "linux" | ||
| 1945 | ], | ||
| 1946 | "engines": { | ||
| 1947 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 1948 | }, | ||
| 1949 | "funding": { | ||
| 1950 | "url": "https://opencollective.com/libvips" | ||
| 1951 | }, | ||
| 1952 | "optionalDependencies": { | ||
| 1953 | "@img/sharp-libvips-linux-arm": "1.2.4" | ||
| 1954 | } | ||
| 1955 | }, | ||
| 1956 | "node_modules/@img/sharp-linux-arm64": { | ||
| 1957 | "version": "0.34.5", | ||
| 1958 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", | ||
| 1959 | "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", | ||
| 1960 | "cpu": [ | ||
| 1961 | "arm64" | ||
| 1962 | ], | ||
| 1963 | "license": "Apache-2.0", | ||
| 1964 | "optional": true, | ||
| 1965 | "os": [ | ||
| 1966 | "linux" | ||
| 1967 | ], | ||
| 1968 | "engines": { | ||
| 1969 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 1970 | }, | ||
| 1971 | "funding": { | ||
| 1972 | "url": "https://opencollective.com/libvips" | ||
| 1973 | }, | ||
| 1974 | "optionalDependencies": { | ||
| 1975 | "@img/sharp-libvips-linux-arm64": "1.2.4" | ||
| 1976 | } | ||
| 1977 | }, | ||
| 1978 | "node_modules/@img/sharp-linux-x64": { | ||
| 1979 | "version": "0.34.5", | ||
| 1980 | "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", | ||
| 1981 | "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", | ||
| 1982 | "cpu": [ | ||
| 1983 | "x64" | ||
| 1984 | ], | ||
| 1985 | "license": "Apache-2.0", | ||
| 1986 | "optional": true, | ||
| 1987 | "os": [ | ||
| 1988 | "linux" | ||
| 1989 | ], | ||
| 1990 | "engines": { | ||
| 1991 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 1992 | }, | ||
| 1993 | "funding": { | ||
| 1994 | "url": "https://opencollective.com/libvips" | ||
| 1995 | }, | ||
| 1996 | "optionalDependencies": { | ||
| 1997 | "@img/sharp-libvips-linux-x64": "1.2.4" | ||
| 1998 | } | ||
| 1999 | }, | ||
| 2000 | "node_modules/@img/sharp-linuxmusl-arm64": { | ||
| 2001 | "version": "0.34.5", | ||
| 2002 | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", | ||
| 2003 | "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", | ||
| 2004 | "cpu": [ | ||
| 2005 | "arm64" | ||
| 2006 | ], | ||
| 2007 | "license": "Apache-2.0", | ||
| 2008 | "optional": true, | ||
| 2009 | "os": [ | ||
| 2010 | "linux" | ||
| 2011 | ], | ||
| 2012 | "engines": { | ||
| 2013 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 2014 | }, | ||
| 2015 | "funding": { | ||
| 2016 | "url": "https://opencollective.com/libvips" | ||
| 2017 | }, | ||
| 2018 | "optionalDependencies": { | ||
| 2019 | "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" | ||
| 2020 | } | ||
| 2021 | }, | ||
| 2022 | "node_modules/@img/sharp-linuxmusl-x64": { | ||
| 2023 | "version": "0.34.5", | ||
| 2024 | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", | ||
| 2025 | "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", | ||
| 2026 | "cpu": [ | ||
| 2027 | "x64" | ||
| 2028 | ], | ||
| 2029 | "license": "Apache-2.0", | ||
| 2030 | "optional": true, | ||
| 2031 | "os": [ | ||
| 2032 | "linux" | ||
| 2033 | ], | ||
| 2034 | "engines": { | ||
| 2035 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 2036 | }, | ||
| 2037 | "funding": { | ||
| 2038 | "url": "https://opencollective.com/libvips" | ||
| 2039 | }, | ||
| 2040 | "optionalDependencies": { | ||
| 2041 | "@img/sharp-libvips-linuxmusl-x64": "1.2.4" | ||
| 2042 | } | ||
| 2043 | }, | ||
| 2044 | "node_modules/@img/sharp-win32-arm64": { | ||
| 2045 | "version": "0.34.5", | ||
| 2046 | "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", | ||
| 2047 | "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", | ||
| 2048 | "cpu": [ | ||
| 2049 | "arm64" | ||
| 2050 | ], | ||
| 2051 | "license": "Apache-2.0 AND LGPL-3.0-or-later", | ||
| 2052 | "optional": true, | ||
| 2053 | "os": [ | ||
| 2054 | "win32" | ||
| 2055 | ], | ||
| 2056 | "engines": { | ||
| 2057 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 2058 | }, | ||
| 2059 | "funding": { | ||
| 2060 | "url": "https://opencollective.com/libvips" | ||
| 2061 | } | ||
| 2062 | }, | ||
| 2063 | "node_modules/@img/sharp-win32-x64": { | ||
| 2064 | "version": "0.34.5", | ||
| 2065 | "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", | ||
| 2066 | "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", | ||
| 2067 | "cpu": [ | ||
| 2068 | "x64" | ||
| 2069 | ], | ||
| 2070 | "license": "Apache-2.0 AND LGPL-3.0-or-later", | ||
| 2071 | "optional": true, | ||
| 2072 | "os": [ | ||
| 2073 | "win32" | ||
| 2074 | ], | ||
| 2075 | "engines": { | ||
| 2076 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | ||
| 2077 | }, | ||
| 2078 | "funding": { | ||
| 2079 | "url": "https://opencollective.com/libvips" | ||
| 2080 | } | ||
| 2081 | }, | ||
| 1752 | "node_modules/@isaacs/balanced-match": { | 2082 | "node_modules/@isaacs/balanced-match": { |
| 1753 | "version": "4.0.1", | 2083 | "version": "4.0.1", |
| 1754 | "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", | 2084 | "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", |
| @@ -2624,6 +2954,13 @@ | |||
| 2624 | "@types/node": "*" | 2954 | "@types/node": "*" |
| 2625 | } | 2955 | } |
| 2626 | }, | 2956 | }, |
| 2957 | "node_modules/@types/uuid": { | ||
| 2958 | "version": "10.0.0", | ||
| 2959 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", | ||
| 2960 | "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", | ||
| 2961 | "dev": true, | ||
| 2962 | "license": "MIT" | ||
| 2963 | }, | ||
| 2627 | "node_modules/@types/verror": { | 2964 | "node_modules/@types/verror": { |
| 2628 | "version": "1.10.11", | 2965 | "version": "1.10.11", |
| 2629 | "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", | 2966 | "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", |
| @@ -7447,6 +7784,19 @@ | |||
| 7447 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", | 7784 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", |
| 7448 | "license": "MIT" | 7785 | "license": "MIT" |
| 7449 | }, | 7786 | }, |
| 7787 | "node_modules/uuid": { | ||
| 7788 | "version": "13.0.0", | ||
| 7789 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", | ||
| 7790 | "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", | ||
| 7791 | "funding": [ | ||
| 7792 | "https://github.com/sponsors/broofa", | ||
| 7793 | "https://github.com/sponsors/ctavan" | ||
| 7794 | ], | ||
| 7795 | "license": "MIT", | ||
| 7796 | "bin": { | ||
| 7797 | "uuid": "dist-node/bin/uuid" | ||
| 7798 | } | ||
| 7799 | }, | ||
| 7450 | "node_modules/verror": { | 7800 | "node_modules/verror": { |
| 7451 | "version": "1.10.1", | 7801 | "version": "1.10.1", |
| 7452 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", | 7802 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", |
| @@ -7706,6 +8056,16 @@ | |||
| 7706 | "funding": { | 8056 | "funding": { |
| 7707 | "url": "https://github.com/sponsors/sindresorhus" | 8057 | "url": "https://github.com/sponsors/sindresorhus" |
| 7708 | } | 8058 | } |
| 8059 | }, | ||
| 8060 | "node_modules/zod": { | ||
| 8061 | "version": "4.3.6", | ||
| 8062 | "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", | ||
| 8063 | "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", | ||
| 8064 | "license": "MIT", | ||
| 8065 | "peer": true, | ||
| 8066 | "funding": { | ||
| 8067 | "url": "https://github.com/sponsors/colinhacks" | ||
| 8068 | } | ||
| 7709 | } | 8069 | } |
| 7710 | } | 8070 | } |
| 7711 | } | 8071 | } |
diff --git a/package.json b/package.json index 2f7e388..a76759a 100644 --- a/package.json +++ b/package.json | |||
| @@ -43,9 +43,11 @@ | |||
| 43 | } | 43 | } |
| 44 | }, | 44 | }, |
| 45 | "dependencies": { | 45 | "dependencies": { |
| 46 | "@anthropic-ai/claude-agent-sdk": "^0.2.63", | ||
| 46 | "better-sqlite3": "12.2.0", | 47 | "better-sqlite3": "12.2.0", |
| 47 | "react": "^19.1.1", | 48 | "react": "^19.1.1", |
| 48 | "react-dom": "^19.1.1" | 49 | "react-dom": "^19.1.1", |
| 50 | "uuid": "^13.0.0" | ||
| 49 | }, | 51 | }, |
| 50 | "devDependencies": { | 52 | "devDependencies": { |
| 51 | "@electron/rebuild": "4.0.1", | 53 | "@electron/rebuild": "4.0.1", |
| @@ -53,6 +55,7 @@ | |||
| 53 | "@types/node": "24.3.2", | 55 | "@types/node": "24.3.2", |
| 54 | "@types/react": "^19.1.13", | 56 | "@types/react": "^19.1.13", |
| 55 | "@types/react-dom": "^19.1.9", | 57 | "@types/react-dom": "^19.1.9", |
| 58 | "@types/uuid": "^10.0.0", | ||
| 56 | "@vitejs/plugin-react": "^5.0.2", | 59 | "@vitejs/plugin-react": "^5.0.2", |
| 57 | "concurrently": "^9.2.1", | 60 | "concurrently": "^9.2.1", |
| 58 | "electron": "38.1.0", | 61 | "electron": "38.1.0", |
diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts new file mode 100644 index 0000000..34a914e --- /dev/null +++ b/src/main/claude/index.ts | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk"; | ||
| 2 | import type { Session } from "../db/sessions"; | ||
| 3 | import { getPhaseConfig, getNextPhase, getArtifactFilename } from "./phases"; | ||
| 4 | import type { Phase, UserPermissionMode } from "./phases"; | ||
| 5 | import { getProject } from "../db/projects"; | ||
| 6 | import { updateSession } from "../db/sessions"; | ||
| 7 | import fs from "node:fs"; | ||
| 8 | import path from "node:path"; | ||
| 9 | |||
| 10 | // Track active queries by session ID | ||
| 11 | const activeQueries = new Map<string, Query>(); | ||
| 12 | |||
| 13 | function ensureArtifactDir(projectPath: string): void { | ||
| 14 | const dir = path.join(projectPath, ".claude-flow"); | ||
| 15 | if (!fs.existsSync(dir)) { | ||
| 16 | fs.mkdirSync(dir, { recursive: true }); | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | export interface SendMessageOptions { | ||
| 21 | session: Session; | ||
| 22 | message: string; | ||
| 23 | onMessage: (msg: SDKMessage) => void; | ||
| 24 | } | ||
| 25 | |||
| 26 | export async function sendMessage({ | ||
| 27 | session, | ||
| 28 | message, | ||
| 29 | onMessage, | ||
| 30 | }: SendMessageOptions): Promise<void> { | ||
| 31 | const project = getProject(session.project_id); | ||
| 32 | if (!project) throw new Error("Project not found"); | ||
| 33 | |||
| 34 | ensureArtifactDir(project.path); | ||
| 35 | |||
| 36 | const phaseConfig = getPhaseConfig( | ||
| 37 | session.phase as Phase, | ||
| 38 | session.permission_mode as UserPermissionMode | ||
| 39 | ); | ||
| 40 | |||
| 41 | const q = query({ | ||
| 42 | prompt: message, | ||
| 43 | options: { | ||
| 44 | cwd: project.path, | ||
| 45 | resume: session.claude_session_id ?? undefined, | ||
| 46 | tools: phaseConfig.tools, | ||
| 47 | permissionMode: phaseConfig.permissionMode, | ||
| 48 | // Add system prompt via extraArgs since there's no direct option | ||
| 49 | extraArgs: { | ||
| 50 | "system-prompt": phaseConfig.systemPrompt, | ||
| 51 | }, | ||
| 52 | }, | ||
| 53 | }); | ||
| 54 | |||
| 55 | activeQueries.set(session.id, q); | ||
| 56 | |||
| 57 | try { | ||
| 58 | for await (const msg of q) { | ||
| 59 | // Capture session ID from init message | ||
| 60 | if (msg.type === "system" && msg.subtype === "init") { | ||
| 61 | if (!session.claude_session_id) { | ||
| 62 | updateSession(session.id, { claude_session_id: msg.session_id }); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | onMessage(msg); | ||
| 66 | } | ||
| 67 | } finally { | ||
| 68 | activeQueries.delete(session.id); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | export function interruptSession(sessionId: string): void { | ||
| 73 | const q = activeQueries.get(sessionId); | ||
| 74 | if (q) { | ||
| 75 | q.close(); | ||
| 76 | activeQueries.delete(sessionId); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Trigger a review: Claude reads the document and addresses user annotations | ||
| 82 | */ | ||
| 83 | export async function triggerReview( | ||
| 84 | session: Session, | ||
| 85 | onMessage: (msg: SDKMessage) => void | ||
| 86 | ): Promise<void> { | ||
| 87 | const docName = getArtifactFilename(session.phase as Phase); | ||
| 88 | const message = `I've updated .claude-flow/${docName} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`; | ||
| 89 | |||
| 90 | await sendMessage({ session, message, onMessage }); | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Advance to the next phase | ||
| 95 | */ | ||
| 96 | export function advancePhase(session: Session): Phase | null { | ||
| 97 | const nextPhase = getNextPhase(session.phase as Phase); | ||
| 98 | if (nextPhase) { | ||
| 99 | updateSession(session.id, { phase: nextPhase }); | ||
| 100 | } | ||
| 101 | return nextPhase; | ||
| 102 | } | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Read an artifact file from the project's .claude-flow directory | ||
| 106 | */ | ||
| 107 | export function readArtifact( | ||
| 108 | projectPath: string, | ||
| 109 | filename: string | ||
| 110 | ): string | null { | ||
| 111 | const filePath = path.join(projectPath, ".claude-flow", filename); | ||
| 112 | if (fs.existsSync(filePath)) { | ||
| 113 | return fs.readFileSync(filePath, "utf-8"); | ||
| 114 | } | ||
| 115 | return null; | ||
| 116 | } | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Write an artifact file to the project's .claude-flow directory | ||
| 120 | */ | ||
| 121 | export function writeArtifact( | ||
| 122 | projectPath: string, | ||
| 123 | filename: string, | ||
| 124 | content: string | ||
| 125 | ): void { | ||
| 126 | const dir = path.join(projectPath, ".claude-flow"); | ||
| 127 | if (!fs.existsSync(dir)) { | ||
| 128 | fs.mkdirSync(dir, { recursive: true }); | ||
| 129 | } | ||
| 130 | fs.writeFileSync(path.join(dir, filename), content, "utf-8"); | ||
| 131 | } | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Get the initial message for a phase | ||
| 135 | */ | ||
| 136 | export function getPhaseInitialMessage(phase: Phase): string { | ||
| 137 | return getPhaseConfig(phase).initialMessage; | ||
| 138 | } | ||
| 139 | |||
| 140 | // Re-export types | ||
| 141 | export type { SDKMessage } from "@anthropic-ai/claude-agent-sdk"; | ||
| 142 | export type { Phase, UserPermissionMode } from "./phases"; | ||
diff --git a/src/main/claude/phases.ts b/src/main/claude/phases.ts new file mode 100644 index 0000000..d503f3a --- /dev/null +++ b/src/main/claude/phases.ts | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk"; | ||
| 2 | |||
| 3 | export type Phase = "research" | "plan" | "implement"; | ||
| 4 | export type UserPermissionMode = "acceptEdits" | "bypassPermissions"; | ||
| 5 | |||
| 6 | export interface PhaseConfig { | ||
| 7 | systemPrompt: string; | ||
| 8 | tools: string[]; | ||
| 9 | permissionMode: PermissionMode; | ||
| 10 | initialMessage: string; | ||
| 11 | } | ||
| 12 | |||
| 13 | export const phaseConfigs: Record<Phase, PhaseConfig> = { | ||
| 14 | research: { | ||
| 15 | permissionMode: "plan", | ||
| 16 | tools: ["Read", "Glob", "Grep", "Bash", "Write"], | ||
| 17 | initialMessage: | ||
| 18 | "What areas of the codebase should I research? What are you trying to build?", | ||
| 19 | systemPrompt: `You are in RESEARCH mode. | ||
| 20 | |||
| 21 | Your job is to deeply understand the codebase before any changes are made. | ||
| 22 | |||
| 23 | When the user tells you what to research: | ||
| 24 | 1. Read files thoroughly — understand all intricacies | ||
| 25 | 2. Write your findings to .claude-flow/research.md | ||
| 26 | 3. Format it as clear, readable markdown | ||
| 27 | |||
| 28 | Rules: | ||
| 29 | - DO NOT make any code changes | ||
| 30 | - DO NOT modify any files except .claude-flow/research.md | ||
| 31 | - Be thorough — surface-level reading is not acceptable | ||
| 32 | |||
| 33 | When the user clicks "Review", read .claude-flow/research.md for their annotations and update accordingly. | ||
| 34 | When the user clicks "Submit", they're ready to move to planning.`, | ||
| 35 | }, | ||
| 36 | |||
| 37 | plan: { | ||
| 38 | permissionMode: "plan", | ||
| 39 | tools: ["Read", "Glob", "Grep", "Write"], | ||
| 40 | initialMessage: | ||
| 41 | "I'll create a detailed implementation plan based on my research. Give me a moment...", | ||
| 42 | systemPrompt: `You are in PLANNING mode. | ||
| 43 | |||
| 44 | Based on the research in .claude-flow/research.md, write a detailed implementation plan. | ||
| 45 | |||
| 46 | Write the plan to .claude-flow/plan.md with: | ||
| 47 | - Detailed explanation of the approach | ||
| 48 | - Specific code snippets showing proposed changes | ||
| 49 | - File paths that will be modified | ||
| 50 | - Trade-offs and considerations | ||
| 51 | - A granular TODO list with checkboxes | ||
| 52 | |||
| 53 | Rules: | ||
| 54 | - DO NOT implement anything | ||
| 55 | - DO NOT modify any source files | ||
| 56 | - Only write to .claude-flow/plan.md | ||
| 57 | |||
| 58 | The plan should be detailed enough that implementation becomes mechanical. | ||
| 59 | |||
| 60 | When the user clicks "Review", read .claude-flow/plan.md for their annotations and update accordingly. | ||
| 61 | When the user clicks "Submit", begin implementation.`, | ||
| 62 | }, | ||
| 63 | |||
| 64 | implement: { | ||
| 65 | permissionMode: "acceptEdits", | ||
| 66 | tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], | ||
| 67 | initialMessage: | ||
| 68 | "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.", | ||
| 69 | systemPrompt: `You are in IMPLEMENTATION mode. The plan has been approved. | ||
| 70 | |||
| 71 | Read .claude-flow/plan.md and execute it: | ||
| 72 | - Follow the plan exactly | ||
| 73 | - Mark tasks complete (- [x]) as you finish them | ||
| 74 | - Run typecheck/lint continuously if available | ||
| 75 | - Do not add unnecessary comments | ||
| 76 | - Do not stop until all tasks are complete | ||
| 77 | |||
| 78 | If you encounter issues not covered by the plan, stop and ask.`, | ||
| 79 | }, | ||
| 80 | }; | ||
| 81 | |||
| 82 | export function getPhaseConfig( | ||
| 83 | phase: Phase, | ||
| 84 | userPermissionMode?: UserPermissionMode | ||
| 85 | ): PhaseConfig { | ||
| 86 | const config = { ...phaseConfigs[phase] }; | ||
| 87 | if (phase === "implement" && userPermissionMode) { | ||
| 88 | config.permissionMode = userPermissionMode; | ||
| 89 | } | ||
| 90 | return config; | ||
| 91 | } | ||
| 92 | |||
| 93 | export function getNextPhase(phase: Phase): Phase | null { | ||
| 94 | const transitions: Record<Phase, Phase | null> = { | ||
| 95 | research: "plan", | ||
| 96 | plan: "implement", | ||
| 97 | implement: null, | ||
| 98 | }; | ||
| 99 | return transitions[phase]; | ||
| 100 | } | ||
| 101 | |||
| 102 | export function getArtifactFilename(phase: Phase): string { | ||
| 103 | return phase === "research" ? "research.md" : "plan.md"; | ||
| 104 | } | ||
diff --git a/src/main/db/index.ts b/src/main/db/index.ts new file mode 100644 index 0000000..a77cdd4 --- /dev/null +++ b/src/main/db/index.ts | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | import Database from "better-sqlite3"; | ||
| 2 | import { app } from "electron"; | ||
| 3 | import path from "node:path"; | ||
| 4 | import fs from "node:fs"; | ||
| 5 | import { initSchema } from "./schema"; | ||
| 6 | |||
| 7 | let db: Database.Database | null = null; | ||
| 8 | |||
| 9 | export function getDb(): Database.Database { | ||
| 10 | if (db) return db; | ||
| 11 | |||
| 12 | const dbDir = app.getPath("userData"); | ||
| 13 | if (!fs.existsSync(dbDir)) { | ||
| 14 | fs.mkdirSync(dbDir, { recursive: true }); | ||
| 15 | } | ||
| 16 | |||
| 17 | const dbPath = path.join(dbDir, "claude-flow.db"); | ||
| 18 | db = new Database(dbPath); | ||
| 19 | db.pragma("journal_mode = WAL"); | ||
| 20 | db.pragma("foreign_keys = ON"); | ||
| 21 | |||
| 22 | initSchema(db); | ||
| 23 | return db; | ||
| 24 | } | ||
| 25 | |||
| 26 | export function closeDb() { | ||
| 27 | if (db) { | ||
| 28 | db.close(); | ||
| 29 | db = null; | ||
| 30 | } | ||
| 31 | } | ||
diff --git a/src/main/db/projects.ts b/src/main/db/projects.ts new file mode 100644 index 0000000..88ef2f6 --- /dev/null +++ b/src/main/db/projects.ts | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | import { getDb } from "./index"; | ||
| 2 | import { v4 as uuid } from "uuid"; | ||
| 3 | |||
| 4 | export interface Project { | ||
| 5 | id: string; | ||
| 6 | name: string; | ||
| 7 | path: string; | ||
| 8 | created_at: number; | ||
| 9 | updated_at: number; | ||
| 10 | } | ||
| 11 | |||
| 12 | export function listProjects(): Project[] { | ||
| 13 | return getDb() | ||
| 14 | .prepare("SELECT * FROM projects ORDER BY updated_at DESC") | ||
| 15 | .all() as Project[]; | ||
| 16 | } | ||
| 17 | |||
| 18 | export function getProject(id: string): Project | undefined { | ||
| 19 | return getDb() | ||
| 20 | .prepare("SELECT * FROM projects WHERE id = ?") | ||
| 21 | .get(id) as Project | undefined; | ||
| 22 | } | ||
| 23 | |||
| 24 | export function createProject(name: string, projectPath: string): Project { | ||
| 25 | const db = getDb(); | ||
| 26 | const id = uuid(); | ||
| 27 | const now = Math.floor(Date.now() / 1000); | ||
| 28 | |||
| 29 | db.prepare( | ||
| 30 | "INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" | ||
| 31 | ).run(id, name, projectPath, now, now); | ||
| 32 | |||
| 33 | return { id, name, path: projectPath, created_at: now, updated_at: now }; | ||
| 34 | } | ||
| 35 | |||
| 36 | export function deleteProject(id: string): void { | ||
| 37 | getDb().prepare("DELETE FROM projects WHERE id = ?").run(id); | ||
| 38 | } | ||
diff --git a/src/main/db/schema.ts b/src/main/db/schema.ts new file mode 100644 index 0000000..c2093f9 --- /dev/null +++ b/src/main/db/schema.ts | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | import Database from "better-sqlite3"; | ||
| 2 | |||
| 3 | export function initSchema(db: Database.Database) { | ||
| 4 | db.exec(` | ||
| 5 | CREATE TABLE IF NOT EXISTS projects ( | ||
| 6 | id TEXT PRIMARY KEY, | ||
| 7 | name TEXT NOT NULL, | ||
| 8 | path TEXT NOT NULL, | ||
| 9 | created_at INTEGER NOT NULL DEFAULT (unixepoch()), | ||
| 10 | updated_at INTEGER NOT NULL DEFAULT (unixepoch()) | ||
| 11 | ); | ||
| 12 | |||
| 13 | CREATE TABLE IF NOT EXISTS sessions ( | ||
| 14 | id TEXT PRIMARY KEY, | ||
| 15 | project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, | ||
| 16 | name TEXT NOT NULL, | ||
| 17 | phase TEXT NOT NULL DEFAULT 'research', | ||
| 18 | claude_session_id TEXT, | ||
| 19 | permission_mode TEXT NOT NULL DEFAULT 'acceptEdits', | ||
| 20 | created_at INTEGER NOT NULL DEFAULT (unixepoch()), | ||
| 21 | updated_at INTEGER NOT NULL DEFAULT (unixepoch()) | ||
| 22 | ); | ||
| 23 | |||
| 24 | CREATE TABLE IF NOT EXISTS messages ( | ||
| 25 | id TEXT PRIMARY KEY, | ||
| 26 | session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, | ||
| 27 | role TEXT NOT NULL, | ||
| 28 | content TEXT NOT NULL, | ||
| 29 | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | ||
| 30 | ); | ||
| 31 | |||
| 32 | CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id); | ||
| 33 | CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id); | ||
| 34 | `); | ||
| 35 | } | ||
diff --git a/src/main/db/sessions.ts b/src/main/db/sessions.ts new file mode 100644 index 0000000..684bb9e --- /dev/null +++ b/src/main/db/sessions.ts | |||
| @@ -0,0 +1,106 @@ | |||
| 1 | import { getDb } from "./index"; | ||
| 2 | import { v4 as uuid } from "uuid"; | ||
| 3 | |||
| 4 | export type Phase = "research" | "plan" | "implement"; | ||
| 5 | export type PermissionMode = "acceptEdits" | "bypassPermissions"; | ||
| 6 | |||
| 7 | export interface Session { | ||
| 8 | id: string; | ||
| 9 | project_id: string; | ||
| 10 | name: string; | ||
| 11 | phase: Phase; | ||
| 12 | claude_session_id: string | null; | ||
| 13 | permission_mode: PermissionMode; | ||
| 14 | created_at: number; | ||
| 15 | updated_at: number; | ||
| 16 | } | ||
| 17 | |||
| 18 | export interface Message { | ||
| 19 | id: string; | ||
| 20 | session_id: string; | ||
| 21 | role: "user" | "assistant"; | ||
| 22 | content: string; | ||
| 23 | created_at: number; | ||
| 24 | } | ||
| 25 | |||
| 26 | export function listSessions(projectId: string): Session[] { | ||
| 27 | return getDb() | ||
| 28 | .prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY updated_at DESC") | ||
| 29 | .all(projectId) as Session[]; | ||
| 30 | } | ||
| 31 | |||
| 32 | export function getSession(id: string): Session | undefined { | ||
| 33 | return getDb() | ||
| 34 | .prepare("SELECT * FROM sessions WHERE id = ?") | ||
| 35 | .get(id) as Session | undefined; | ||
| 36 | } | ||
| 37 | |||
| 38 | export function createSession(projectId: string, name: string): Session { | ||
| 39 | const db = getDb(); | ||
| 40 | const id = uuid(); | ||
| 41 | const now = Math.floor(Date.now() / 1000); | ||
| 42 | |||
| 43 | db.prepare( | ||
| 44 | `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at) | ||
| 45 | VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)` | ||
| 46 | ).run(id, projectId, name, now, now); | ||
| 47 | |||
| 48 | return { | ||
| 49 | id, | ||
| 50 | project_id: projectId, | ||
| 51 | name, | ||
| 52 | phase: "research", | ||
| 53 | claude_session_id: null, | ||
| 54 | permission_mode: "acceptEdits", | ||
| 55 | created_at: now, | ||
| 56 | updated_at: now, | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | |||
| 60 | export function updateSession( | ||
| 61 | id: string, | ||
| 62 | updates: Partial<Pick<Session, "name" | "phase" | "claude_session_id" | "permission_mode">> | ||
| 63 | ): void { | ||
| 64 | const db = getDb(); | ||
| 65 | const sets: string[] = []; | ||
| 66 | const values: any[] = []; | ||
| 67 | |||
| 68 | for (const [key, value] of Object.entries(updates)) { | ||
| 69 | if (value !== undefined) { | ||
| 70 | sets.push(`${key} = ?`); | ||
| 71 | values.push(value); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | if (sets.length > 0) { | ||
| 76 | sets.push("updated_at = ?"); | ||
| 77 | values.push(Math.floor(Date.now() / 1000)); | ||
| 78 | values.push(id); | ||
| 79 | db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | export function deleteSession(id: string): void { | ||
| 84 | getDb().prepare("DELETE FROM sessions WHERE id = ?").run(id); | ||
| 85 | } | ||
| 86 | |||
| 87 | // Messages | ||
| 88 | export function listMessages(sessionId: string): Message[] { | ||
| 89 | return getDb() | ||
| 90 | .prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC") | ||
| 91 | .all(sessionId) as Message[]; | ||
| 92 | } | ||
| 93 | |||
| 94 | export function addMessage(sessionId: string, role: Message["role"], content: string): Message { | ||
| 95 | const db = getDb(); | ||
| 96 | const id = uuid(); | ||
| 97 | const now = Math.floor(Date.now() / 1000); | ||
| 98 | |||
| 99 | db.prepare( | ||
| 100 | "INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)" | ||
| 101 | ).run(id, sessionId, role, content, now); | ||
| 102 | |||
| 103 | db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId); | ||
| 104 | |||
| 105 | return { id, session_id: sessionId, role, content, created_at: now }; | ||
| 106 | } | ||
