Big upgrade to windows and linux compability, also introduced a auto upgrading functionality to work cross platform with also the posiblity for Git init on app startup and checking when adding new projects.
This commit is contained in:
@@ -5,13 +5,21 @@ import { resolve } from 'path';
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin({
|
||||
exclude: []
|
||||
// Bundle these packages into the main process (they won't be in node_modules in packaged app)
|
||||
exclude: [
|
||||
'uuid',
|
||||
'chokidar',
|
||||
'ioredis',
|
||||
'electron-updater',
|
||||
'@electron-toolkit/utils'
|
||||
]
|
||||
})],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/main/index.ts')
|
||||
},
|
||||
// Only node-pty needs to be external (native module rebuilt by electron-builder)
|
||||
external: ['node-pty']
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+40
-38
@@ -42,7 +42,7 @@
|
||||
"ioredis": "^5.8.2",
|
||||
"lucide-react": "^0.560.0",
|
||||
"motion": "^12.23.26",
|
||||
"node-pty": "^1.0.0",
|
||||
"node-pty": "^1.1.0-beta42",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -150,6 +150,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -535,6 +536,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -558,6 +560,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -597,6 +600,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -991,7 +995,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cross-dirname": "^0.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1013,7 +1016,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
@@ -3928,8 +3930,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4126,6 +4127,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -4136,6 +4138,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4227,6 +4230,7 @@
|
||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.49.0",
|
||||
"@typescript-eslint/types": "8.49.0",
|
||||
@@ -4626,7 +4630,8 @@
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/7zip-bin": {
|
||||
"version": "5.2.0",
|
||||
@@ -4648,6 +4653,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4708,6 +4714,7 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -4880,7 +4887,6 @@
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
@@ -5265,6 +5271,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -5944,8 +5951,7 @@
|
||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
@@ -6289,6 +6295,7 @@
|
||||
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.0.12",
|
||||
"builder-util": "26.0.11",
|
||||
@@ -6346,8 +6353,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
@@ -6423,6 +6429,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^22.7.7",
|
||||
@@ -6551,7 +6558,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -6572,7 +6578,6 @@
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -6588,7 +6593,6 @@
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@@ -6599,7 +6603,6 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
@@ -6969,6 +6972,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8988,6 +8992,7 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -9931,7 +9936,6 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -11198,12 +11202,6 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz",
|
||||
"integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nano-spawn": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
||||
@@ -11285,15 +11283,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-pty": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||
"version": "1.1.0-beta42",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta42.tgz",
|
||||
"integrity": "sha512-59KoV6xxhJciRVpo4lQ9wnP38SPaBlXgwszYS8nlHAHrt02d14peg+kHtJ4AOtyLWiCf8WPCeJNbxBkiA7Oy7Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nan": "^2.17.0"
|
||||
"node-addon-api": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pty/node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
@@ -11771,6 +11775,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -11868,6 +11873,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11904,7 +11910,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.4.0"
|
||||
},
|
||||
@@ -11922,7 +11927,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
@@ -11943,7 +11947,6 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -11959,7 +11962,6 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -11972,8 +11974,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proc-log": {
|
||||
"version": "2.0.1",
|
||||
@@ -12077,6 +12078,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12086,6 +12088,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -13402,7 +13405,8 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -13459,7 +13463,6 @@
|
||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "~2.6.2"
|
||||
@@ -13486,7 +13489,6 @@
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -13508,7 +13510,6 @@
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -13522,7 +13523,6 @@
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
@@ -13537,7 +13537,6 @@
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
@@ -13854,6 +13853,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14194,6 +14194,7 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -15227,6 +15228,7 @@
|
||||
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"ioredis": "^5.8.2",
|
||||
"lucide-react": "^0.560.0",
|
||||
"motion": "^12.23.26",
|
||||
"node-pty": "^1.0.0",
|
||||
"node-pty": "^1.1.0-beta42",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -127,7 +127,18 @@
|
||||
"buildResources": "resources"
|
||||
},
|
||||
"files": [
|
||||
"out/**/*"
|
||||
"out/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "node_modules/node-pty",
|
||||
"to": "node_modules/node-pty"
|
||||
},
|
||||
{
|
||||
"from": "resources/icon.ico",
|
||||
"to": "icon.ico"
|
||||
}
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools",
|
||||
@@ -138,7 +149,7 @@
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"icon": "resources/icon-256.png",
|
||||
"icon": "resources/icon.ico",
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -176,7 +176,8 @@ export class AgentProcessManager {
|
||||
...extraEnv,
|
||||
...profileEnv, // Include active Claude profile config
|
||||
PYTHONUNBUFFERED: '1', // Ensure real-time output
|
||||
PYTHONIOENCODING: 'utf-8' // Ensure UTF-8 encoding on Windows
|
||||
PYTHONIOENCODING: 'utf-8', // Ensure UTF-8 encoding on Windows
|
||||
PYTHONUTF8: '1' // Force Python UTF-8 mode on Windows (Python 3.7+)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -241,17 +242,17 @@ export class AgentProcessManager {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle stdout
|
||||
// Handle stdout - explicitly decode as UTF-8 for cross-platform Unicode support
|
||||
childProcess.stdout?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
console.log('[spawnProcess] stdout:', log.substring(0, 200));
|
||||
this.emitter.emit('log', taskId, log);
|
||||
processLog(log);
|
||||
});
|
||||
|
||||
// Handle stderr
|
||||
// Handle stderr - explicitly decode as UTF-8 for cross-platform Unicode support
|
||||
childProcess.stderr?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
console.log('[spawnProcess] stderr:', log.substring(0, 200));
|
||||
// Some Python output goes to stderr (like progress bars)
|
||||
// so we treat it as log, not error
|
||||
|
||||
@@ -156,7 +156,9 @@ export class AgentQueueManager {
|
||||
...process.env,
|
||||
...combinedEnv,
|
||||
...profileEnv,
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -193,9 +195,9 @@ export class AgentQueueManager {
|
||||
const completedTypes = new Set<string>();
|
||||
const totalTypes = 7; // Default all types
|
||||
|
||||
// Handle stdout
|
||||
// Handle stdout - explicitly decode as UTF-8 for cross-platform Unicode support
|
||||
childProcess.stdout?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
// Collect output for rate limit detection (keep last 10KB)
|
||||
allOutput = (allOutput + log).slice(-10000);
|
||||
|
||||
@@ -242,9 +244,9 @@ export class AgentQueueManager {
|
||||
});
|
||||
});
|
||||
|
||||
// Handle stderr - also emit as logs
|
||||
// Handle stderr - also emit as logs, explicitly decode as UTF-8
|
||||
childProcess.stderr?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
// Collect stderr for rate limit detection too
|
||||
allOutput = (allOutput + log).slice(-10000);
|
||||
console.error('[Ideation STDERR]', log);
|
||||
@@ -354,7 +356,9 @@ export class AgentQueueManager {
|
||||
...process.env,
|
||||
...combinedEnv,
|
||||
...profileEnv,
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -384,9 +388,9 @@ export class AgentQueueManager {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle stdout
|
||||
// Handle stdout - explicitly decode as UTF-8 for cross-platform Unicode support
|
||||
childProcess.stdout?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
// Collect output for rate limit detection (keep last 10KB)
|
||||
allRoadmapOutput = (allRoadmapOutput + log).slice(-10000);
|
||||
|
||||
@@ -406,9 +410,9 @@ export class AgentQueueManager {
|
||||
});
|
||||
});
|
||||
|
||||
// Handle stderr
|
||||
// Handle stderr - explicitly decode as UTF-8
|
||||
childProcess.stderr?.on('data', (data: Buffer) => {
|
||||
const log = data.toString();
|
||||
const log = data.toString('utf8');
|
||||
// Collect stderr for rate limit detection too
|
||||
allRoadmapOutput = (allRoadmapOutput + log).slice(-10000);
|
||||
console.error('[Roadmap STDERR]', log);
|
||||
|
||||
@@ -277,7 +277,9 @@ export class ChangelogGenerator extends EventEmitter {
|
||||
USER: process.env.USER || process.env.USERNAME || 'user',
|
||||
// Add common binary locations to PATH for claude CLI
|
||||
PATH: [process.env.PATH || '', ...pathAdditions].filter(Boolean).join(path.delimiter),
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
};
|
||||
|
||||
this.debug('Spawn environment', {
|
||||
|
||||
@@ -237,7 +237,9 @@ except Exception as e:
|
||||
...(isWindows ? { USERPROFILE: homeDir } : { HOME: homeDir }),
|
||||
USER: process.env.USER || process.env.USERNAME || 'user',
|
||||
PATH: [process.env.PATH || '', ...pathAdditions].filter(Boolean).join(path.delimiter),
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
};
|
||||
|
||||
return spawnEnv;
|
||||
|
||||
@@ -22,7 +22,7 @@ function getIconPath(): string {
|
||||
// Use PNG in dev mode (works better), ICNS in production
|
||||
iconName = is.dev ? 'icon-256.png' : 'icon.icns';
|
||||
} else if (process.platform === 'win32') {
|
||||
iconName = 'icon-256.png';
|
||||
iconName = 'icon.ico';
|
||||
} else {
|
||||
iconName = 'icon.png';
|
||||
}
|
||||
|
||||
@@ -105,7 +105,8 @@ export class InsightsConfig {
|
||||
...autoBuildEnv,
|
||||
...profileEnv,
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8'
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,16 @@ import type {
|
||||
ProjectSettings,
|
||||
IPCResult,
|
||||
InitializationResult,
|
||||
AutoBuildVersionInfo
|
||||
AutoBuildVersionInfo,
|
||||
GitStatus
|
||||
} from '../../shared/types';
|
||||
import { projectStore } from '../project-store';
|
||||
import {
|
||||
initializeProject,
|
||||
isInitialized,
|
||||
hasLocalSource
|
||||
hasLocalSource,
|
||||
checkGitStatus,
|
||||
initializeGit
|
||||
} from '../project-initializer';
|
||||
import { PythonEnvManager, type PythonEnvStatus } from '../python-env-manager';
|
||||
import { AgentManager } from '../agent';
|
||||
@@ -505,4 +508,42 @@ export function registerProjectHandlers(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Check git status for a project (is it a repo? has commits?)
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.GIT_CHECK_STATUS,
|
||||
async (_, projectPath: string): Promise<IPCResult<GitStatus>> => {
|
||||
try {
|
||||
if (!existsSync(projectPath)) {
|
||||
return { success: false, error: 'Directory does not exist' };
|
||||
}
|
||||
const gitStatus = checkGitStatus(projectPath);
|
||||
return { success: true, data: gitStatus };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize git in a project (run git init and create initial commit)
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.GIT_INITIALIZE,
|
||||
async (_, projectPath: string): Promise<IPCResult<InitializationResult>> => {
|
||||
try {
|
||||
if (!existsSync(projectPath)) {
|
||||
return { success: false, error: 'Directory does not exist' };
|
||||
}
|
||||
const result = initializeGit(projectPath);
|
||||
return { success: result.success, data: result, error: result.error };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { AgentManager } from '../../agent';
|
||||
import { fileWatcher } from '../../file-watcher';
|
||||
import { findTaskAndProject } from './shared';
|
||||
import { checkGitStatus } from '../../project-initializer';
|
||||
|
||||
/**
|
||||
* Register task execution handlers (start, stop, review, status management, recovery)
|
||||
@@ -40,6 +41,27 @@ export function registerTaskExecutionHandlers(
|
||||
return;
|
||||
}
|
||||
|
||||
// Check git status - Auto Claude requires git for worktree-based builds
|
||||
const gitStatus = checkGitStatus(project.path);
|
||||
if (!gitStatus.isGitRepo) {
|
||||
console.log('[TASK_START] Project is not a git repository:', project.path);
|
||||
mainWindow.webContents.send(
|
||||
IPC_CHANNELS.TASK_ERROR,
|
||||
taskId,
|
||||
'Git repository required. Please run "git init" in your project directory. Auto Claude uses git worktrees for isolated builds.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!gitStatus.hasCommits) {
|
||||
console.log('[TASK_START] Git repository has no commits:', project.path);
|
||||
mainWindow.webContents.send(
|
||||
IPC_CHANNELS.TASK_ERROR,
|
||||
taskId,
|
||||
'Git repository has no commits. Please make an initial commit first (git add . && git commit -m "Initial commit").'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[TASK_START] Found task:', task.specId, 'status:', task.status, 'subtasks:', task.subtasks.length);
|
||||
|
||||
// Start file watcher for this task
|
||||
@@ -287,6 +309,21 @@ export function registerTaskExecutionHandlers(
|
||||
// Auto-start task when status changes to 'in_progress' and no process is running
|
||||
if (status === 'in_progress' && !agentManager.isRunning(taskId)) {
|
||||
const mainWindow = getMainWindow();
|
||||
|
||||
// Check git status before auto-starting
|
||||
const gitStatusCheck = checkGitStatus(project.path);
|
||||
if (!gitStatusCheck.isGitRepo || !gitStatusCheck.hasCommits) {
|
||||
console.log('[TASK_UPDATE_STATUS] Git check failed, cannot auto-start task');
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(
|
||||
IPC_CHANNELS.TASK_ERROR,
|
||||
taskId,
|
||||
gitStatusCheck.error || 'Git repository with commits required to run tasks.'
|
||||
);
|
||||
}
|
||||
return { success: false, error: gitStatusCheck.error || 'Git repository required' };
|
||||
}
|
||||
|
||||
console.log('[TASK_UPDATE_STATUS] Auto-starting task:', taskId);
|
||||
|
||||
// Start file watcher for this task
|
||||
@@ -498,6 +535,23 @@ export function registerTaskExecutionHandlers(
|
||||
// Auto-restart the task if requested
|
||||
let autoRestarted = false;
|
||||
if (autoRestart && project) {
|
||||
// Check git status before auto-restarting
|
||||
const gitStatusForRestart = checkGitStatus(project.path);
|
||||
if (!gitStatusForRestart.isGitRepo || !gitStatusForRestart.hasCommits) {
|
||||
console.log('[Recovery] Git check failed, cannot auto-restart task');
|
||||
// Recovery succeeded but we can't restart without git
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
taskId,
|
||||
recovered: true,
|
||||
newStatus,
|
||||
message: `Task recovered but cannot restart: ${gitStatusForRestart.error || 'Git repository with commits required.'}`,
|
||||
autoRestarted: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Set status to in_progress for the restart
|
||||
newStatus = 'in_progress';
|
||||
|
||||
@@ -315,7 +315,9 @@ export function registerWorktreeHandlers(
|
||||
env: {
|
||||
...process.env,
|
||||
...profileEnv, // Include active Claude profile OAuth token
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'] // Don't connect stdin to avoid blocking
|
||||
});
|
||||
@@ -568,7 +570,7 @@ export function registerWorktreeHandlers(
|
||||
return new Promise((resolve) => {
|
||||
const previewProcess = spawn(pythonPath, args, {
|
||||
cwd: sourcePath,
|
||||
env: { ...process.env, ...previewProfileEnv, PYTHONUNBUFFERED: '1', DEBUG: 'true' }
|
||||
env: { ...process.env, ...previewProfileEnv, PYTHONUNBUFFERED: '1', PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1', DEBUG: 'true' }
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/**
|
||||
* Debug logging - only logs when AUTO_CLAUDE_DEBUG env var is set
|
||||
@@ -16,6 +17,137 @@ function debug(message: string, data?: Record<string, unknown>): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Git status information for a project
|
||||
*/
|
||||
export interface GitStatus {
|
||||
isGitRepo: boolean;
|
||||
hasCommits: boolean;
|
||||
currentBranch: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory is a git repository and has at least one commit
|
||||
*/
|
||||
export function checkGitStatus(projectPath: string): GitStatus {
|
||||
try {
|
||||
// Check if it's a git repository
|
||||
execSync('git rev-parse --git-dir', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
} catch {
|
||||
return {
|
||||
isGitRepo: false,
|
||||
hasCommits: false,
|
||||
currentBranch: null,
|
||||
error: 'Not a git repository. Please run "git init" to initialize git.'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if there are any commits
|
||||
let hasCommits = false;
|
||||
try {
|
||||
execSync('git rev-parse HEAD', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
hasCommits = true;
|
||||
} catch {
|
||||
// No commits yet
|
||||
hasCommits = false;
|
||||
}
|
||||
|
||||
// Get current branch
|
||||
let currentBranch: string | null = null;
|
||||
try {
|
||||
currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
} catch {
|
||||
// Branch detection failed
|
||||
}
|
||||
|
||||
if (!hasCommits) {
|
||||
return {
|
||||
isGitRepo: true,
|
||||
hasCommits: false,
|
||||
currentBranch,
|
||||
error: 'Git repository has no commits. Please make an initial commit first.'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isGitRepo: true,
|
||||
hasCommits: true,
|
||||
currentBranch
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize git in a project directory and create an initial commit.
|
||||
* This is a user-friendly way to set up git for non-technical users.
|
||||
*/
|
||||
export function initializeGit(projectPath: string): InitializationResult {
|
||||
debug('initializeGit called', { projectPath });
|
||||
|
||||
// Check current git status
|
||||
const status = checkGitStatus(projectPath);
|
||||
|
||||
try {
|
||||
// Step 1: Initialize git if needed
|
||||
if (!status.isGitRepo) {
|
||||
debug('Initializing git repository');
|
||||
execSync('git init', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2: Check if there are files to commit
|
||||
const statusOutput = execSync('git status --porcelain', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
|
||||
// Step 3: If there are untracked/modified files, add and commit them
|
||||
if (statusOutput || !status.hasCommits) {
|
||||
debug('Adding files and creating initial commit');
|
||||
|
||||
// Add all files
|
||||
execSync('git add -A', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// Create initial commit
|
||||
execSync('git commit -m "Initial commit" --allow-empty', {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
}
|
||||
|
||||
debug('Git initialization complete');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error during git initialization';
|
||||
debug('Git initialization failed', { error: errorMessage });
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries to add to .gitignore when initializing a project
|
||||
*/
|
||||
@@ -130,6 +262,10 @@ export function isInitialized(projectPath: string): boolean {
|
||||
*
|
||||
* Creates .auto-claude/ with data directories (specs, ideation, insights, roadmap).
|
||||
* The framework code runs from the source repo - only data is stored here.
|
||||
*
|
||||
* Requires:
|
||||
* - Project directory must exist
|
||||
* - Project must be a git repository with at least one commit
|
||||
*/
|
||||
export function initializeProject(projectPath: string): InitializationResult {
|
||||
debug('initializeProject called', { projectPath });
|
||||
@@ -143,6 +279,16 @@ export function initializeProject(projectPath: string): InitializationResult {
|
||||
};
|
||||
}
|
||||
|
||||
// Check git status - Auto Claude requires git for worktree-based builds
|
||||
const gitStatus = checkGitStatus(projectPath);
|
||||
if (!gitStatus.isGitRepo || !gitStatus.hasCommits) {
|
||||
debug('Git check failed', { gitStatus });
|
||||
return {
|
||||
success: false,
|
||||
error: gitStatus.error || 'Git repository required. Auto Claude uses git worktrees for isolated builds.'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if already initialized
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
|
||||
|
||||
@@ -137,7 +137,8 @@ export class TerminalNameGenerator extends EventEmitter {
|
||||
...autoBuildEnv,
|
||||
...profileEnv, // Include active Claude profile config
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8'
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -136,7 +136,8 @@ export class TitleGenerator extends EventEmitter {
|
||||
...autoBuildEnv,
|
||||
...profileEnv, // Include active Claude profile config
|
||||
PYTHONUNBUFFERED: '1',
|
||||
PYTHONIOENCODING: 'utf-8'
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
PYTHONUTF8: '1'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import type {
|
||||
ClaudeAuthResult,
|
||||
InfrastructureStatus,
|
||||
GraphitiValidationResult,
|
||||
GraphitiConnectionTestResult
|
||||
GraphitiConnectionTestResult,
|
||||
GitStatus
|
||||
} from '../../shared/types';
|
||||
|
||||
export interface ProjectAPI {
|
||||
@@ -72,6 +73,8 @@ export interface ProjectAPI {
|
||||
getGitBranches: (projectPath: string) => Promise<IPCResult<string[]>>;
|
||||
getCurrentGitBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||
detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||
checkGitStatus: (projectPath: string) => Promise<IPCResult<GitStatus>>;
|
||||
initializeGit: (projectPath: string) => Promise<IPCResult<InitializationResult>>;
|
||||
}
|
||||
|
||||
export const createProjectAPI = (): ProjectAPI => ({
|
||||
@@ -180,5 +183,11 @@ export const createProjectAPI = (): ProjectAPI => ({
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GIT_GET_CURRENT_BRANCH, projectPath),
|
||||
|
||||
detectMainBranch: (projectPath: string): Promise<IPCResult<string | null>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GIT_DETECT_MAIN_BRANCH, projectPath)
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GIT_DETECT_MAIN_BRANCH, projectPath),
|
||||
|
||||
checkGitStatus: (projectPath: string): Promise<IPCResult<GitStatus>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GIT_CHECK_STATUS, projectPath),
|
||||
|
||||
initializeGit: (projectPath: string): Promise<IPCResult<InitializationResult>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GIT_INITIALIZE, projectPath)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
import { useState } from 'react';
|
||||
import { GitBranch, Terminal, CheckCircle2, AlertCircle, Loader2, FolderGit2 } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from './ui/dialog';
|
||||
import type { Project, GitStatus } from '../../shared/types';
|
||||
|
||||
interface GitSetupModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
project: Project | null;
|
||||
gitStatus: GitStatus | null;
|
||||
onGitInitialized: () => void;
|
||||
onSkip?: () => void;
|
||||
}
|
||||
|
||||
export function GitSetupModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
project,
|
||||
gitStatus,
|
||||
onGitInitialized,
|
||||
onSkip
|
||||
}: GitSetupModalProps) {
|
||||
const [isInitializing, setIsInitializing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [step, setStep] = useState<'info' | 'initializing' | 'success'>('info');
|
||||
|
||||
const needsGitInit = gitStatus && !gitStatus.isGitRepo;
|
||||
const needsCommit = gitStatus && gitStatus.isGitRepo && !gitStatus.hasCommits;
|
||||
|
||||
const handleInitializeGit = async () => {
|
||||
if (!project) return;
|
||||
|
||||
setIsInitializing(true);
|
||||
setError(null);
|
||||
setStep('initializing');
|
||||
|
||||
try {
|
||||
// Call the backend to initialize git
|
||||
const result = await window.electronAPI.initializeGit(project.path);
|
||||
|
||||
if (result.success) {
|
||||
setStep('success');
|
||||
// Wait a moment to show success, then trigger callback
|
||||
setTimeout(() => {
|
||||
onGitInitialized();
|
||||
onOpenChange(false);
|
||||
setStep('info');
|
||||
}, 1500);
|
||||
} else {
|
||||
setError(result.error || 'Failed to initialize git');
|
||||
setStep('info');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to initialize git');
|
||||
setStep('info');
|
||||
} finally {
|
||||
setIsInitializing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
onSkip?.();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const renderInfoStep = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderGit2 className="h-5 w-5 text-primary" />
|
||||
Git Repository Required
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Auto Claude uses git to safely build features in isolated workspaces
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4 space-y-4">
|
||||
{/* Status indicator */}
|
||||
<div className="rounded-lg bg-muted p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-warning mt-0.5 shrink-0" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-sm">
|
||||
{needsGitInit
|
||||
? 'This folder is not a git repository'
|
||||
: 'Git repository has no commits'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{needsGitInit
|
||||
? 'Git needs to be initialized before Auto Claude can manage your code.'
|
||||
: 'At least one commit is required for Auto Claude to create worktrees.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* What will happen */}
|
||||
<div className="rounded-lg border border-border p-4">
|
||||
<p className="font-medium text-sm mb-3">We'll set up git for you:</p>
|
||||
<ul className="space-y-2">
|
||||
{needsGitInit && (
|
||||
<li className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<GitBranch className="h-4 w-4 text-primary" />
|
||||
Initialize a new git repository
|
||||
</li>
|
||||
)}
|
||||
<li className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 className="h-4 w-4 text-primary" />
|
||||
Create an initial commit with your current files
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Manual instructions for advanced users */}
|
||||
<details className="text-sm">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
Prefer to do it manually?
|
||||
</summary>
|
||||
<div className="mt-3 rounded-lg bg-muted/50 p-3 font-mono text-xs space-y-1">
|
||||
<p className="text-muted-foreground">Open a terminal in your project folder and run:</p>
|
||||
{needsGitInit && <p>git init</p>}
|
||||
<p>git add .</p>
|
||||
<p>git commit -m "Initial commit"</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-lg bg-destructive/10 border border-destructive/20 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleSkip}>
|
||||
Skip for now
|
||||
</Button>
|
||||
<Button onClick={handleInitializeGit} disabled={isInitializing}>
|
||||
<GitBranch className="mr-2 h-4 w-4" />
|
||||
Initialize Git
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderInitializingStep = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-primary" />
|
||||
Setting up Git
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-8 flex flex-col items-center justify-center">
|
||||
<div className="space-y-3 text-center">
|
||||
<Terminal className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Initializing git repository and creating initial commit...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderSuccessStep = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<CheckCircle2 className="h-5 w-5 text-success" />
|
||||
Git Initialized
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-8 flex flex-col items-center justify-center">
|
||||
<div className="space-y-3 text-center">
|
||||
<div className="h-16 w-16 rounded-full bg-success/10 flex items-center justify-center mx-auto">
|
||||
<CheckCircle2 className="h-8 w-8 text-success" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your project is now ready to use with Auto Claude!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
{step === 'info' && renderInfoStep()}
|
||||
{step === 'initializing' && renderInitializingStep()}
|
||||
{step === 'success' && renderSuccessStep()}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -55,8 +55,9 @@ import {
|
||||
} from '../stores/project-store';
|
||||
import { useSettingsStore, saveSettings } from '../stores/settings-store';
|
||||
import { AddProjectModal } from './AddProjectModal';
|
||||
import { GitSetupModal } from './GitSetupModal';
|
||||
import { RateLimitIndicator } from './RateLimitIndicator';
|
||||
import type { Project, AutoBuildVersionInfo } from '../../shared/types';
|
||||
import type { Project, AutoBuildVersionInfo, GitStatus } from '../../shared/types';
|
||||
|
||||
export type SidebarView = 'kanban' | 'terminals' | 'roadmap' | 'context' | 'ideation' | 'github-issues' | 'changelog' | 'insights' | 'worktrees' | 'agent-tools' | 'agent-profiles';
|
||||
|
||||
@@ -104,6 +105,8 @@ export function Sidebar({
|
||||
const [showAddProjectModal, setShowAddProjectModal] = useState(false);
|
||||
const [showInitDialog, setShowInitDialog] = useState(false);
|
||||
const [showUpdateDialog, setShowUpdateDialog] = useState(false);
|
||||
const [showGitSetupModal, setShowGitSetupModal] = useState(false);
|
||||
const [gitStatus, setGitStatus] = useState<GitStatus | null>(null);
|
||||
const [pendingProject, setPendingProject] = useState<Project | null>(null);
|
||||
const [versionInfo, setVersionInfo] = useState<AutoBuildVersionInfo | null>(null);
|
||||
const [isInitializing, setIsInitializing] = useState(false);
|
||||
@@ -159,6 +162,29 @@ export function Sidebar({
|
||||
checkUpdates();
|
||||
}, [selectedProjectId, settings.autoUpdateAutoBuild]);
|
||||
|
||||
// Check git status when project changes
|
||||
useEffect(() => {
|
||||
const checkGit = async () => {
|
||||
if (selectedProject) {
|
||||
try {
|
||||
const result = await window.electronAPI.checkGitStatus(selectedProject.path);
|
||||
if (result.success && result.data) {
|
||||
setGitStatus(result.data);
|
||||
// Show git setup modal if project is not a git repo or has no commits
|
||||
if (!result.data.isGitRepo || !result.data.hasCommits) {
|
||||
setShowGitSetupModal(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check git status:', error);
|
||||
}
|
||||
} else {
|
||||
setGitStatus(null);
|
||||
}
|
||||
};
|
||||
checkGit();
|
||||
}, [selectedProject]);
|
||||
|
||||
const handleAddProject = () => {
|
||||
setShowAddProjectModal(true);
|
||||
};
|
||||
@@ -210,6 +236,20 @@ export function Sidebar({
|
||||
setVersionInfo(null);
|
||||
};
|
||||
|
||||
const handleGitInitialized = async () => {
|
||||
// Refresh git status after initialization
|
||||
if (selectedProject) {
|
||||
try {
|
||||
const result = await window.electronAPI.checkGitStatus(selectedProject.path);
|
||||
if (result.success && result.data) {
|
||||
setGitStatus(result.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh git status:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveProject = async (projectId: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -518,6 +558,15 @@ export function Sidebar({
|
||||
onOpenChange={setShowAddProjectModal}
|
||||
onProjectAdded={handleProjectAdded}
|
||||
/>
|
||||
|
||||
{/* Git Setup Modal */}
|
||||
<GitSetupModal
|
||||
open={showGitSetupModal}
|
||||
onOpenChange={setShowGitSetupModal}
|
||||
project={selectedProject || null}
|
||||
gitStatus={gitStatus}
|
||||
onGitInitialized={handleGitInitialized}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,5 +84,19 @@ export const projectMock = {
|
||||
detectMainBranch: async () => ({
|
||||
success: true,
|
||||
data: 'main'
|
||||
}),
|
||||
|
||||
checkGitStatus: async () => ({
|
||||
success: true,
|
||||
data: {
|
||||
isGitRepo: true,
|
||||
hasCommits: true,
|
||||
currentBranch: 'main'
|
||||
}
|
||||
}),
|
||||
|
||||
initializeGit: async () => ({
|
||||
success: true,
|
||||
data: { success: true }
|
||||
})
|
||||
};
|
||||
|
||||
@@ -258,6 +258,8 @@ export const IPC_CHANNELS = {
|
||||
GIT_GET_BRANCHES: 'git:getBranches',
|
||||
GIT_GET_CURRENT_BRANCH: 'git:getCurrentBranch',
|
||||
GIT_DETECT_MAIN_BRANCH: 'git:detectMainBranch',
|
||||
GIT_CHECK_STATUS: 'git:checkStatus',
|
||||
GIT_INITIALIZE: 'git:initialize',
|
||||
|
||||
// App auto-update operations
|
||||
APP_UPDATE_CHECK: 'app-update:check',
|
||||
|
||||
@@ -15,8 +15,6 @@ export * from './changelog';
|
||||
export * from './insights';
|
||||
export * from './roadmap';
|
||||
export * from './integrations';
|
||||
export * from './glossary';
|
||||
export * from './status';
|
||||
export * from './app-update';
|
||||
|
||||
// IPC types (must be last to use types from other modules)
|
||||
|
||||
@@ -18,7 +18,8 @@ import type {
|
||||
ProjectEnvConfig,
|
||||
InfrastructureStatus,
|
||||
GraphitiValidationResult,
|
||||
GraphitiConnectionTestResult
|
||||
GraphitiConnectionTestResult,
|
||||
GitStatus
|
||||
} from './project';
|
||||
import type {
|
||||
Task,
|
||||
@@ -500,6 +501,8 @@ export interface ElectronAPI {
|
||||
getGitBranches: (projectPath: string) => Promise<IPCResult<string[]>>;
|
||||
getCurrentGitBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||
detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
|
||||
checkGitStatus: (projectPath: string) => Promise<IPCResult<GitStatus>>;
|
||||
initializeGit: (projectPath: string) => Promise<IPCResult<InitializationResult>>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -305,6 +305,13 @@ export interface InitializationResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface GitStatus {
|
||||
isGitRepo: boolean;
|
||||
hasCommits: boolean;
|
||||
currentBranch: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface CreateProjectFolderResult {
|
||||
path: string;
|
||||
name: string;
|
||||
|
||||
@@ -79,11 +79,15 @@ class WorktreeManager:
|
||||
cwd=self.project_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return env_branch
|
||||
else:
|
||||
print(f"Warning: DEFAULT_BRANCH '{env_branch}' not found, auto-detecting...")
|
||||
print(
|
||||
f"Warning: DEFAULT_BRANCH '{env_branch}' not found, auto-detecting..."
|
||||
)
|
||||
|
||||
# 2. Auto-detect main/master
|
||||
for branch in ["main", "master"]:
|
||||
@@ -92,15 +96,17 @@ class WorktreeManager:
|
||||
cwd=self.project_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return branch
|
||||
|
||||
# 3. Fall back to current branch with warning
|
||||
current = self._get_current_branch()
|
||||
print(f"Warning: Could not find 'main' or 'master' branch.")
|
||||
print("Warning: Could not find 'main' or 'master' branch.")
|
||||
print(f"Warning: Using current branch '{current}' as base for worktree.")
|
||||
print(f"Tip: Set DEFAULT_BRANCH=your-branch in .env to avoid this.")
|
||||
print("Tip: Set DEFAULT_BRANCH=your-branch in .env to avoid this.")
|
||||
return current
|
||||
|
||||
def _get_current_branch(self) -> str:
|
||||
@@ -110,6 +116,8 @@ class WorktreeManager:
|
||||
cwd=self.project_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise WorktreeError(f"Failed to get current branch: {result.stderr}")
|
||||
@@ -124,6 +132,8 @@ class WorktreeManager:
|
||||
cwd=cwd or self.project_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
|
||||
def _unstage_gitignored_files(self) -> None:
|
||||
@@ -153,6 +163,8 @@ class WorktreeManager:
|
||||
input="\n".join(staged_files),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
|
||||
if result.stdout.strip():
|
||||
|
||||
@@ -105,8 +105,8 @@ class ImplementationPlan:
|
||||
self.update_status_from_subtasks()
|
||||
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "w") as f:
|
||||
json.dump(self.to_dict(), f, indent=2)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
||||
|
||||
def update_status_from_subtasks(self):
|
||||
"""Update overall status and planStatus based on subtask completion state.
|
||||
@@ -169,7 +169,7 @@ class ImplementationPlan:
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> "ImplementationPlan":
|
||||
"""Load plan from JSON file."""
|
||||
with open(path) as f:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return cls.from_dict(json.load(f))
|
||||
|
||||
def get_available_phases(self) -> list[Phase]:
|
||||
|
||||
@@ -28,6 +28,38 @@ Prerequisites:
|
||||
- Claude Code CLI installed
|
||||
"""
|
||||
|
||||
import io
|
||||
import sys
|
||||
|
||||
# Configure safe encoding on Windows BEFORE any imports that might print
|
||||
# This handles both TTY and piped output (e.g., from Electron)
|
||||
if sys.platform == "win32":
|
||||
for _stream_name in ("stdout", "stderr"):
|
||||
_stream = getattr(sys, _stream_name)
|
||||
# Method 1: Try reconfigure (works for TTY)
|
||||
if hasattr(_stream, "reconfigure"):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
continue
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
# Method 2: Wrap with TextIOWrapper for piped output
|
||||
try:
|
||||
if hasattr(_stream, "buffer"):
|
||||
_new_stream = io.TextIOWrapper(
|
||||
_stream.buffer,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
line_buffering=True,
|
||||
)
|
||||
setattr(sys, _stream_name, _new_stream)
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
# Clean up temporary variables
|
||||
del _stream_name, _stream
|
||||
if "_new_stream" in dir():
|
||||
del _new_stream
|
||||
|
||||
from cli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -34,10 +34,40 @@ Usage:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Configure safe encoding on Windows BEFORE any imports that might print
|
||||
# This handles both TTY and piped output (e.g., from Electron)
|
||||
if sys.platform == "win32":
|
||||
for _stream_name in ("stdout", "stderr"):
|
||||
_stream = getattr(sys, _stream_name)
|
||||
# Method 1: Try reconfigure (works for TTY)
|
||||
if hasattr(_stream, "reconfigure"):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
continue
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
# Method 2: Wrap with TextIOWrapper for piped output
|
||||
try:
|
||||
if hasattr(_stream, "buffer"):
|
||||
_new_stream = io.TextIOWrapper(
|
||||
_stream.buffer,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
line_buffering=True,
|
||||
)
|
||||
setattr(sys, _stream_name, _new_stream)
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
# Clean up temporary variables
|
||||
del _stream_name, _stream
|
||||
if "_new_stream" in dir():
|
||||
del _new_stream
|
||||
|
||||
# Add auto-claude to path (parent of runners/)
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
@@ -230,7 +260,9 @@ Examples:
|
||||
print(
|
||||
f" {muted('Or re-run spec_runner with --auto-approve to skip review:')}"
|
||||
)
|
||||
example_cmd = 'python auto-claude/spec_runner.py --task "..." --auto-approve'
|
||||
example_cmd = (
|
||||
'python auto-claude/spec_runner.py --task "..." --auto-approve'
|
||||
)
|
||||
print(f" {highlight(example_cmd)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ Handles the execution of AI agents for the spec creation pipeline.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Configure safe encoding before any output (fixes Windows encoding errors)
|
||||
from ui.capabilities import configure_safe_encoding
|
||||
|
||||
configure_safe_encoding()
|
||||
|
||||
from client import create_client
|
||||
from task_logger import (
|
||||
LogEntryType,
|
||||
|
||||
@@ -457,9 +457,11 @@ class SpecOrchestrator:
|
||||
print_key_value("Reasoning", assessment.reasoning)
|
||||
|
||||
if assessment.needs_research:
|
||||
print(f" {muted('→ Research phase enabled')}")
|
||||
print(f" {muted(icon(Icons.ARROW_RIGHT) + ' Research phase enabled')}")
|
||||
if assessment.needs_self_critique:
|
||||
print(f" {muted('→ Self-critique phase enabled')}")
|
||||
print(
|
||||
f" {muted(icon(Icons.ARROW_RIGHT) + ' Self-critique phase enabled')}"
|
||||
)
|
||||
|
||||
def _print_phases_to_run(self) -> None:
|
||||
"""Print the list of phases that will be executed."""
|
||||
|
||||
@@ -30,16 +30,16 @@ class ValidationResult:
|
||||
if self.errors:
|
||||
lines.append("\nErrors:")
|
||||
for err in self.errors:
|
||||
lines.append(f" ✗ {err}")
|
||||
lines.append(f" [X] {err}")
|
||||
|
||||
if self.warnings:
|
||||
lines.append("\nWarnings:")
|
||||
for warn in self.warnings:
|
||||
lines.append(f" ⚠ {warn}")
|
||||
lines.append(f" [!] {warn}")
|
||||
|
||||
if self.fixes and not self.valid:
|
||||
lines.append("\nSuggested Fixes:")
|
||||
for fix in self.fixes:
|
||||
lines.append(f" → {fix}")
|
||||
lines.append(f" -> {fix}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -30,7 +30,7 @@ class LogStorage:
|
||||
"""Load existing logs or create new structure."""
|
||||
if self.log_file.exists():
|
||||
try:
|
||||
with open(self.log_file) as f:
|
||||
with open(self.log_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
pass
|
||||
@@ -69,8 +69,8 @@ class LogStorage:
|
||||
self._data["updated_at"] = self._timestamp()
|
||||
try:
|
||||
self.spec_dir.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.log_file, "w") as f:
|
||||
json.dump(self._data, f, indent=2)
|
||||
with open(self.log_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f, indent=2, ensure_ascii=False)
|
||||
except OSError as e:
|
||||
print(f"Warning: Failed to save task logs: {e}", file=sys.stderr)
|
||||
|
||||
@@ -159,7 +159,7 @@ def load_task_logs(spec_dir: Path) -> dict | None:
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(log_file) as f:
|
||||
with open(log_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
|
||||
@@ -21,6 +21,7 @@ from .capabilities import (
|
||||
FANCY_UI,
|
||||
INTERACTIVE,
|
||||
UNICODE,
|
||||
configure_safe_encoding,
|
||||
supports_color,
|
||||
supports_interactive,
|
||||
supports_unicode,
|
||||
@@ -57,6 +58,7 @@ _INTERACTIVE = INTERACTIVE
|
||||
|
||||
__all__ = [
|
||||
# Capabilities
|
||||
"configure_safe_encoding",
|
||||
"supports_unicode",
|
||||
"supports_color",
|
||||
"supports_interactive",
|
||||
|
||||
@@ -8,10 +8,56 @@ Detects terminal capabilities for:
|
||||
- Interactive input support
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def configure_safe_encoding() -> None:
|
||||
"""
|
||||
Configure stdout/stderr to handle Unicode safely on Windows.
|
||||
|
||||
On Windows, the default console encoding (cp1252) can't display many
|
||||
Unicode characters. This function forces UTF-8 encoding with 'replace'
|
||||
error handling, so unrenderable characters are replaced with '?' instead
|
||||
of raising exceptions.
|
||||
|
||||
This handles both:
|
||||
1. Regular console output (reconfigure method)
|
||||
2. Piped output from subprocess (TextIOWrapper replacement)
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
return
|
||||
|
||||
# Method 1: Try reconfigure (works for TTY)
|
||||
for stream_name in ("stdout", "stderr"):
|
||||
stream = getattr(sys, stream_name)
|
||||
if hasattr(stream, "reconfigure"):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
continue
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
|
||||
# Method 2: Wrap with TextIOWrapper for piped output
|
||||
# This is needed when stdout/stderr are pipes (e.g., from Electron)
|
||||
try:
|
||||
if hasattr(stream, "buffer"):
|
||||
new_stream = io.TextIOWrapper(
|
||||
stream.buffer,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
line_buffering=True,
|
||||
)
|
||||
setattr(sys, stream_name, new_stream)
|
||||
except (AttributeError, io.UnsupportedOperation, OSError):
|
||||
pass
|
||||
|
||||
|
||||
# Configure safe encoding on module import
|
||||
configure_safe_encoding()
|
||||
|
||||
|
||||
def _is_fancy_ui_enabled() -> bool:
|
||||
"""Check if fancy UI is enabled via environment variable."""
|
||||
value = os.environ.get("ENABLE_FANCY_UI", "true").lower()
|
||||
|
||||
Reference in New Issue
Block a user