binary-collections 2.0.9 → 2.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.puppeterrc.cjs +25 -0
  2. package/binaries/binary-executor.cjs +178 -0
  3. package/{bin → binaries}/clean-nodemodule +0 -0
  4. package/binaries/clean-nodemodule.cjs +178 -0
  5. package/binaries/clean-nodemodule.cmd +4 -0
  6. package/{bin → binaries}/clean-nodemodules +0 -0
  7. package/binaries/clean-nodemodules.cjs +178 -0
  8. package/binaries/clean-nodemodules.cmd +5 -0
  9. package/{bin → binaries}/dev +0 -0
  10. package/binaries/dev.cjs +178 -0
  11. package/{bin → binaries}/empty +0 -0
  12. package/binaries/empty.cjs +178 -0
  13. package/{bin → binaries}/git-reduce-size +0 -0
  14. package/binaries/git-reduce-size.cjs +178 -0
  15. package/binaries/javakill.cjs +178 -0
  16. package/{bin → binaries}/javakill.cmd +0 -0
  17. package/binaries/kill-night-crows.bat +7 -0
  18. package/binaries/kill-night-crows.ps1 +172 -0
  19. package/{bin → binaries}/kill-process +0 -0
  20. package/binaries/kill-process.cjs +178 -0
  21. package/binaries/nodekill.cjs +178 -0
  22. package/{bin → binaries}/nodekill.ps1 +0 -0
  23. package/{bin → binaries}/prod +0 -0
  24. package/binaries/prod.cjs +178 -0
  25. package/binaries/py +111 -0
  26. package/binaries/py.cjs +178 -0
  27. package/binaries/py.cmd +49 -0
  28. package/{bin → binaries}/rmfind +0 -0
  29. package/binaries/rmfind.cjs +178 -0
  30. package/{bin → binaries}/rmx +0 -0
  31. package/binaries/rmx.cjs +178 -0
  32. package/{bin → binaries}/submodule-token +0 -0
  33. package/binaries/submodule-token.cjs +178 -0
  34. package/binaries/test-cjs +10 -0
  35. package/binaries/test-cjs.cjs +178 -0
  36. package/binaries/test-cjs.cmd +11 -0
  37. package/binaries/yarn-clean +32 -0
  38. package/binaries/yarn-clean.cjs +178 -0
  39. package/binaries/yarn-clean.cmd +30 -0
  40. package/binaries/yarn-clean.py +148 -0
  41. package/docs-src/clean-github-actions-caches.md +26 -0
  42. package/docs-src/free-chatgpt.md +26 -0
  43. package/lib/binary-collections-config.cjs +3 -2
  44. package/lib/binary-collections-config.mjs +2 -2
  45. package/lib/binary-collections.cjs +513 -105
  46. package/lib/binary-collections.mjs +178 -117
  47. package/lib/changelog.cjs +64 -16
  48. package/lib/changelog.mjs +177 -171
  49. package/lib/{chunk-VVEZVNIV.mjs → chunk-2CBJCW7E.mjs} +3 -3
  50. package/lib/chunk-34IQDTLZ.mjs +27 -0
  51. package/lib/chunk-3HFFECCI.mjs +27 -0
  52. package/lib/chunk-4UHL4WVN.mjs +136 -0
  53. package/lib/{chunk-4BYBVEYC.mjs → chunk-66PAU5PS.mjs} +5 -4
  54. package/lib/chunk-6HHJRKFB.mjs +59 -0
  55. package/lib/chunk-6S4NXESK.mjs +26 -0
  56. package/lib/chunk-7XTEJHOE.mjs +193 -0
  57. package/lib/{chunk-ZYAQRPUL.mjs → chunk-7YD7IPFF.mjs} +2 -2
  58. package/lib/{chunk-SH3L6HHV.mjs → chunk-A2JQXI5Z.mjs} +2 -2
  59. package/lib/{chunk-EGSSKVDH.mjs → chunk-A3VUZEJK.mjs} +1 -1
  60. package/lib/chunk-AJDD5DZM.mjs +109 -0
  61. package/lib/chunk-BZWVHODJ.mjs +62 -0
  62. package/lib/chunk-FCDQGYBF.mjs +136 -0
  63. package/lib/chunk-FKI7IEB5.mjs +172 -0
  64. package/lib/chunk-GEYA2USY.mjs +207 -0
  65. package/lib/chunk-GJTGHXRA.mjs +356 -0
  66. package/lib/{chunk-YV7DO3YV.mjs → chunk-HLGOWBEO.mjs} +1 -1
  67. package/lib/chunk-ID2WBTE2.mjs +80 -0
  68. package/lib/chunk-JXFOHKDM.mjs +239 -0
  69. package/lib/{chunk-YX5U7XDR.mjs → chunk-M3YIYRHT.mjs} +6 -5
  70. package/lib/chunk-N436BNBK.mjs +514 -0
  71. package/lib/chunk-NCXAP7AA.mjs +31 -0
  72. package/lib/chunk-PDN26I7O.mjs +188 -0
  73. package/lib/{chunk-JGR2NW6D.mjs → chunk-PDSXF5HY.mjs} +3 -3
  74. package/lib/{chunk-AASHBCRW.mjs → chunk-QQ4A6DLD.mjs} +8 -0
  75. package/lib/{chunk-ONIBBBQ3.mjs → chunk-RWLXRTYP.mjs} +4 -3
  76. package/lib/chunk-TOIVAQF7.mjs +136 -0
  77. package/lib/chunk-V5SKYJUB.mjs +136 -0
  78. package/lib/chunk-WSRETQCA.mjs +59 -0
  79. package/lib/chunk-XA3SNBPA.mjs +184 -0
  80. package/lib/chunk-XVBFFVCJ.mjs +209 -0
  81. package/lib/chunk-YYLIQQKF.mjs +31 -0
  82. package/lib/{chunk-APBWENF6.mjs → chunk-Z6JLYU2J.mjs} +63 -16
  83. package/lib/chunk-ZDMWBSYF.mjs +81 -0
  84. package/lib/clean-github-actions-caches.cjs +291 -148
  85. package/lib/clean-github-actions-caches.mjs +4 -3
  86. package/lib/del-gradle.cjs +63 -15
  87. package/lib/del-gradle.js +2 -1
  88. package/lib/del-gradle.mjs +2 -2
  89. package/lib/del-node-modules.cjs +143 -148
  90. package/lib/del-node-modules.js +210 -14
  91. package/lib/del-node-modules.mjs +149 -18
  92. package/lib/del-ps.cjs +90 -21
  93. package/lib/del-ps.js +3 -2
  94. package/lib/del-ps.mjs +7 -5
  95. package/lib/del-yarn-caches.cjs +87 -18
  96. package/lib/del-yarn-caches.js +38 -3
  97. package/lib/del-yarn-caches.mjs +6 -6
  98. package/lib/find-node-modules-cli.cjs +5 -4
  99. package/lib/find-node-modules-cli.js +2 -1
  100. package/lib/find-node-modules-cli.mjs +2 -2
  101. package/lib/find-node-modules.cjs +4 -3
  102. package/lib/{find-node-modules.d.ts → find-node-modules.d.cts} +1 -1
  103. package/lib/find-node-modules.mjs +2 -2
  104. package/lib/free-chatgpt.cjs +754 -0
  105. package/lib/free-chatgpt.js +51 -0
  106. package/lib/free-chatgpt.mjs +50 -0
  107. package/lib/git/gitattributes.cjs +2 -1
  108. package/lib/git/{gitattributes.d.ts → gitattributes.d.cts} +7 -2
  109. package/lib/git/gitattributes.mjs +2 -2
  110. package/lib/git/line-endings.cjs +298 -64
  111. package/lib/git/line-endings.mjs +4 -4
  112. package/lib/git/normalize.cjs +26 -36
  113. package/lib/git/normalize.mjs +2 -2
  114. package/lib/git/permissions.cjs +77 -11
  115. package/lib/git/permissions.mjs +3 -3
  116. package/lib/git/pull-strategy.cjs +76 -9
  117. package/lib/git/pull-strategy.mjs +3 -3
  118. package/lib/git/undo-commit-cli.cjs +110 -0
  119. package/lib/git/undo-commit-cli.d.ts +1 -0
  120. package/lib/git/undo-commit-cli.js +4 -0
  121. package/lib/git/undo-commit-cli.mjs +14 -0
  122. package/lib/git/undo-commit.cjs +81 -0
  123. package/lib/git/undo-commit.d.cts +1 -0
  124. package/lib/git/undo-commit.mjs +7 -0
  125. package/lib/git/undo-staged-cli.cjs +110 -0
  126. package/lib/git/undo-staged-cli.d.ts +1 -0
  127. package/lib/git/undo-staged-cli.js +4 -0
  128. package/lib/git/undo-staged-cli.mjs +14 -0
  129. package/lib/git/undo-staged.cjs +81 -0
  130. package/lib/git/undo-staged.d.cts +1 -0
  131. package/lib/git/undo-staged.mjs +7 -0
  132. package/lib/git/user-config.cjs +313 -83
  133. package/lib/git/user-config.mjs +4 -4
  134. package/lib/git/utils.cjs +40 -60
  135. package/lib/git/utils.mjs +2 -2
  136. package/lib/git-diff-cli.cjs +857 -0
  137. package/lib/git-diff-cli.js +16 -0
  138. package/lib/git-diff-cli.mjs +17 -0
  139. package/lib/git-diff.cjs +862 -58
  140. package/lib/git-diff.d.ts +38 -83
  141. package/lib/git-diff.js +152 -0
  142. package/lib/git-diff.mjs +23 -85
  143. package/lib/git-fix.cjs +733 -97
  144. package/lib/git-fix.mjs +10 -9
  145. package/lib/git-purge.cjs +64 -16
  146. package/lib/git-purge.d.cts +1 -0
  147. package/lib/git-purge.mjs +43 -37
  148. package/lib/index.cjs +7 -6
  149. package/lib/index.d.ts +1 -1
  150. package/lib/index.js +2 -5
  151. package/lib/index.mjs +4 -4
  152. package/lib/kill-night-crows.cjs +87 -0
  153. package/lib/kill-night-crows.d.mts +1 -0
  154. package/lib/kill-night-crows.mjs +65 -0
  155. package/lib/npm-run-series.cjs +63 -16
  156. package/lib/npm-run-series.mjs +42 -36
  157. package/lib/package-resolutions-updater-cli.cjs +560 -0
  158. package/lib/package-resolutions-updater-cli.d.mts +1 -0
  159. package/lib/package-resolutions-updater-cli.mjs +124 -0
  160. package/lib/package-resolutions-updater.cjs +178 -158
  161. package/lib/package-resolutions-updater.d.mts +32 -1
  162. package/lib/package-resolutions-updater.mjs +17 -338
  163. package/lib/php-cs-fixer-staged.cjs +105 -0
  164. package/lib/php-cs-fixer-staged.d.cts +2 -0
  165. package/lib/php-cs-fixer-staged.mjs +117 -0
  166. package/lib/print-directory-tree.cjs +320 -207
  167. package/lib/print-directory-tree.mjs +3 -3
  168. package/lib/ps/connected-domain.cjs +25 -2
  169. package/lib/ps/connected-domain.d.ts +10 -2
  170. package/lib/ps/connected-domain.js +5 -2
  171. package/lib/ps/connected-domain.mjs +8 -4
  172. package/lib/ps/index.cjs +345 -322
  173. package/lib/ps/index.d.mjs +1 -1
  174. package/lib/ps/index.js +2 -1
  175. package/lib/ps/index.mjs +179 -182
  176. package/lib/ps/isWin.cjs +24 -1
  177. package/lib/ps/isWin.d.ts +1 -1
  178. package/lib/ps/isWin.js +3 -1
  179. package/lib/ps/isWin.mjs +8 -4
  180. package/lib/ps/table-parser.cjs +167 -159
  181. package/lib/ps/table-parser.d.ts +5 -0
  182. package/lib/ps/table-parser.js +10 -4
  183. package/lib/ps/table-parser.mjs +9 -5
  184. package/lib/remove-module.cjs +310 -0
  185. package/lib/remove-module.d.mts +1 -0
  186. package/lib/remove-module.mjs +111 -0
  187. package/lib/rmpath.cjs +322 -0
  188. package/lib/rmpath.d.mts +3 -0
  189. package/lib/rmpath.mjs +108 -0
  190. package/lib/submodule-install.cjs +311 -86
  191. package/lib/submodule-install.mjs +53 -5
  192. package/lib/submodule-remove-cli.cjs +107 -0
  193. package/lib/submodule-remove-cli.d.ts +1 -0
  194. package/lib/submodule-remove-cli.js +31 -0
  195. package/lib/submodule-remove-cli.mjs +28 -0
  196. package/lib/submodule-remove.cjs +46 -0
  197. package/lib/submodule-remove.d.cts +2 -0
  198. package/lib/submodule-remove.mjs +6 -0
  199. package/lib/utils/chatgpt.cjs +541 -0
  200. package/lib/utils/chatgpt.d.ts +31 -0
  201. package/lib/utils/chatgpt.js +708 -0
  202. package/lib/utils/chatgpt.mjs +8 -0
  203. package/lib/utils/findEnvFiles.cjs +107 -0
  204. package/lib/utils/findEnvFiles.d.ts +8 -0
  205. package/lib/utils/findEnvFiles.js +121 -0
  206. package/lib/utils/findEnvFiles.mjs +8 -0
  207. package/lib/utils/findWorkspaceRoot.cjs +70 -0
  208. package/lib/utils/findWorkspaceRoot.d.ts +9 -0
  209. package/lib/utils/findWorkspaceRoot.js +57 -0
  210. package/lib/utils/findWorkspaceRoot.mjs +40 -0
  211. package/lib/{utils.cjs → utils/index.cjs} +61 -14
  212. package/lib/{utils.mjs → utils/index.mjs} +2 -2
  213. package/lib/utils/isGithubTokenValid.cjs +64 -0
  214. package/lib/utils/isGithubTokenValid.d.ts +7 -0
  215. package/lib/utils/isGithubTokenValid.js +48 -0
  216. package/lib/utils/isGithubTokenValid.mjs +36 -0
  217. package/lib/yarn-reinstall.cjs +220 -38
  218. package/lib/yarn-reinstall.mjs +2 -2
  219. package/package.json +138 -104
  220. package/readme.md +48 -75
  221. package/releases/readme.md +36 -0
  222. package/requirements.txt +1 -0
  223. package/test/README.md +101 -0
  224. package/test/package.json +2 -1
  225. package/test-project/readme.md +26 -0
  226. package/tmp/test-repo/README.md +35 -0
  227. package/tmp/test-repo/package.json +1 -1
  228. package/tmp/typedoc/readme.md +320 -0
  229. package/bin/bash-dummy +0 -56
  230. package/bin/bash-dummy.cmd +0 -25
  231. package/bin/dir-tree.cmd +0 -7
  232. package/bin/git-diff +0 -4
  233. package/bin/git-diff.cmd +0 -7
  234. package/bin/git-fix +0 -36
  235. package/bin/git-fix.cmd +0 -7
  236. package/bin/rmpath +0 -70
  237. package/bin/submodule-install.txt +0 -118
  238. package/bin/submodule-remove +0 -46
  239. package/bin/submodule.txt +0 -172
  240. package/lib/binary-collections-config.d.mts +0 -18
  241. package/lib/binary-collections-config.js +0 -39
  242. package/lib/binary-collections.d.mts +0 -137
  243. package/lib/binary-collections.d.ts +0 -137
  244. package/lib/changelog.d.mts +0 -2
  245. package/lib/changelog.js +0 -226
  246. package/lib/chunk-DPKAJKFO.mjs +0 -171
  247. package/lib/chunk-G3THLIDT.mjs +0 -200
  248. package/lib/chunk-W3ENOM53.mjs +0 -18
  249. package/lib/clean-github-actions-caches.d.mts +0 -169
  250. package/lib/clean-github-actions-caches.d.ts +0 -169
  251. package/lib/del-gradle.d.mts +0 -2
  252. package/lib/del-node-modules.d.mts +0 -2
  253. package/lib/del-ps.d.mts +0 -2
  254. package/lib/del-yarn-caches.d.mts +0 -2
  255. package/lib/find-node-modules-cli.d.mts +0 -1
  256. package/lib/find-node-modules.d.mts +0 -13
  257. package/lib/find-node-modules.js +0 -53
  258. package/lib/git/gitattributes.d.mts +0 -35
  259. package/lib/git/gitattributes.js +0 -223
  260. package/lib/git/line-endings.d.mts +0 -83
  261. package/lib/git/line-endings.d.ts +0 -83
  262. package/lib/git/normalize.d.mts +0 -43
  263. package/lib/git/normalize.d.ts +0 -43
  264. package/lib/git/permissions.d.mts +0 -17
  265. package/lib/git/permissions.d.ts +0 -17
  266. package/lib/git/pull-strategy.d.mts +0 -15
  267. package/lib/git/pull-strategy.d.ts +0 -15
  268. package/lib/git/user-config.d.mts +0 -105
  269. package/lib/git/user-config.d.ts +0 -105
  270. package/lib/git/utils.d.mts +0 -69
  271. package/lib/git/utils.d.ts +0 -69
  272. package/lib/git-diff.d.mts +0 -84
  273. package/lib/git-fix.d.mts +0 -141
  274. package/lib/git-fix.d.ts +0 -141
  275. package/lib/git-purge.d.mts +0 -2
  276. package/lib/git-purge.js +0 -59
  277. package/lib/index.d.mts +0 -1
  278. package/lib/npm-run-series.d.mts +0 -1
  279. package/lib/npm-run-series.js +0 -86
  280. package/lib/package-resolutions-updater.d.ts +0 -352
  281. package/lib/print-directory-tree.d.mts +0 -234
  282. package/lib/print-directory-tree.d.ts +0 -234
  283. package/lib/ps/connected-domain.d.mts +0 -3
  284. package/lib/ps/index.d.d.mts +0 -26
  285. package/lib/ps/index.d.d.ts +0 -26
  286. package/lib/ps/index.d.mts +0 -26
  287. package/lib/ps/isWin.d.mts +0 -3
  288. package/lib/ps/table-parser.d.mts +0 -3
  289. package/lib/submodule-install.d.mts +0 -121
  290. package/lib/submodule-install.d.ts +0 -121
  291. package/lib/utils.d.mts +0 -40
  292. package/lib/utils.js +0 -181
  293. package/lib/yarn-reinstall.d.mts +0 -49
  294. package/lib/yarn-reinstall.d.ts +0 -49
  295. package/src/package-resolutions-updater.mjs +0 -350
  296. package/src/print-directory-tree.cjs +0 -234
  297. package/src/ps/index.js +0 -286
  298. package/src/yarn-reinstall.cjs +0 -49
  299. /package/{bin → binaries}/nodekill +0 -0
  300. /package/{bin → binaries}/nodekill.cmd +0 -0
  301. /package/lib/{binary-collections-config.d.ts → binary-collections-config.d.cts} +0 -0
  302. /package/lib/{changelog.d.ts → changelog.d.cts} +0 -0
  303. /package/lib/{git-purge.d.ts → free-chatgpt.d.ts} +0 -0
  304. /package/lib/{git-diff.d.cts → git-diff-cli.d.ts} +0 -0
  305. /package/lib/{npm-run-series.d.ts → npm-run-series.d.cts} +0 -0
  306. /package/lib/{utils.d.ts → utils/index.d.cts} +0 -0
@@ -0,0 +1,708 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.runChatGpt = runChatGpt;
16
+ const fs_extra_1 = __importDefault(require("fs-extra"));
17
+ const puppeteer_extra_1 = __importDefault(require("puppeteer-extra"));
18
+ const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth"));
19
+ const upath_1 = __importDefault(require("upath"));
20
+ const COOKIE_DIR = upath_1.default.join(process.cwd(), "tmp", "cookies");
21
+ const DEFAULT_COOKIE_PATH = upath_1.default.join(COOKIE_DIR, "cookies.json");
22
+ const NAVIGATION_TIMEOUT_MS = 90000;
23
+ const NETWORK_IDLE_TIMEOUT_MS = 15000;
24
+ const MAX_INLINE_QUESTION_FILE_BYTES = 2 * 1024;
25
+ fs_extra_1.default.ensureDirSync(COOKIE_DIR);
26
+ /**
27
+ * Navigates to a page with a resilient strategy for apps that keep long-lived network connections.
28
+ *
29
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
30
+ * @param {string} url - URL to navigate to.
31
+ * @returns {Promise<void>} Resolves when the page is at least DOM-ready.
32
+ */
33
+ function gotoWithFallback(page, url) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ yield page.goto(url, { waitUntil: "domcontentloaded", timeout: NAVIGATION_TIMEOUT_MS });
36
+ // Best effort: settle initial bursty requests without hard-failing on persistent streams.
37
+ try {
38
+ yield page.waitForNetworkIdle({ idleTime: 1000, timeout: NETWORK_IDLE_TIMEOUT_MS });
39
+ }
40
+ catch (_a) {
41
+ // Ignore network-idle timeouts because ChatGPT keeps active connections open.
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Saves cookies from a Puppeteer page to a specified file path.
47
+ *
48
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
49
+ * @param {string} [path=DEFAULT_COOKIE_PATH] - Path to save the cookies file.
50
+ * @returns {Promise<void>} Resolves when cookies are saved.
51
+ */
52
+ function saveCookies(page_1) {
53
+ return __awaiter(this, arguments, void 0, function* (page, path = DEFAULT_COOKIE_PATH) {
54
+ const cookies = yield page.cookies();
55
+ fs_extra_1.default.writeFileSync(path, JSON.stringify(cookies, null, 2));
56
+ });
57
+ }
58
+ /**
59
+ * Returns the cookie file path for a given URL's hostname.
60
+ *
61
+ * @param {string} url - The URL to extract the hostname from.
62
+ * @returns {string} The path to the cookie file for the hostname, or the default cookie path if invalid.
63
+ */
64
+ function getCookiePathForUrl(url) {
65
+ try {
66
+ const { hostname } = new URL(url);
67
+ return upath_1.default.join(COOKIE_DIR, `cookies_${hostname}.json`);
68
+ }
69
+ catch (_a) {
70
+ return DEFAULT_COOKIE_PATH;
71
+ }
72
+ }
73
+ /**
74
+ * Navigates to a URL using Puppeteer, loading cookies for the host and injecting a DOM mutation observer.
75
+ *
76
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
77
+ * @param {string} url - The URL to navigate to.
78
+ * @returns {Promise<{ waitForDomIdle: (idleMs?: number, timeout?: number) => Promise<boolean> }>} An object containing a function to wait for DOM stability.
79
+ */
80
+ function navigatePage(page, url) {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ const cookiePath = getCookiePathForUrl(url);
83
+ // Load cookies for the host
84
+ const cookies = loadCookies(cookiePath);
85
+ if (cookies) {
86
+ yield page.setCookie(...cookies);
87
+ }
88
+ // Navigate with fallback for pages that keep persistent network connections.
89
+ yield gotoWithFallback(page, url);
90
+ // Inject DOM mutation observer to handle dynamic content
91
+ yield page.evaluate(() => {
92
+ window.__domStillUpdating = true;
93
+ if (window.__domObserver) {
94
+ window.__domObserver.disconnect();
95
+ }
96
+ window.__domObserver = new MutationObserver(() => {
97
+ window.__lastDomMutation = Date.now();
98
+ });
99
+ window.__lastDomMutation = Date.now();
100
+ window.__domObserver.observe(document.body, {
101
+ childList: true,
102
+ subtree: true,
103
+ attributes: true,
104
+ characterData: true
105
+ });
106
+ // Stop tracking after a while (optional)
107
+ setTimeout(() => {
108
+ window.__domStillUpdating = false;
109
+ window.__domObserver.disconnect();
110
+ }, 30000); // e.g. 30 seconds max
111
+ });
112
+ /**
113
+ * Waits until the DOM has been stable (no mutations) for a specified number of milliseconds.
114
+ *
115
+ * @param {number} [idleMs=1000] - The number of milliseconds the DOM must be stable.
116
+ * @param {number} [timeout=10000] - The maximum time to wait for the DOM to stabilize.
117
+ * @returns {Promise<boolean>} Resolves to true if the DOM was stable for idleMs within timeout, otherwise throws an error.
118
+ */
119
+ const waitForDomIdle = (...args_1) => __awaiter(this, [...args_1], void 0, function* (idleMs = 1000, timeout = 10000) {
120
+ const start = Date.now();
121
+ while (Date.now() - start < timeout) {
122
+ const lastMutation = yield page.evaluate(() => window.__lastDomMutation);
123
+ const idle = Date.now() - lastMutation;
124
+ if (idle >= idleMs) {
125
+ return true; // DOM has been stable for idleMs
126
+ }
127
+ yield new Promise((r) => setTimeout(r, 200)); // poll every 200ms
128
+ }
129
+ throw new Error("DOM did not stabilize within timeout");
130
+ });
131
+ return { waitForDomIdle };
132
+ });
133
+ }
134
+ /**
135
+ * Loads cookies from a specified file path.
136
+ *
137
+ * @param {string} [cookieFilePath=DEFAULT_COOKIE_PATH] - Path to the cookie file.
138
+ * @returns {Array|Null} Parsed cookies array, or null if file does not exist.
139
+ */
140
+ function loadCookies(cookieFilePath = DEFAULT_COOKIE_PATH) {
141
+ if (!fs_extra_1.default.existsSync(cookieFilePath))
142
+ return null;
143
+ return JSON.parse(fs_extra_1.default.readFileSync(cookieFilePath));
144
+ }
145
+ /**
146
+ * Restores cookies from a file to a Puppeteer page.
147
+ *
148
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
149
+ * @param {string} [cookieFilePath=DEFAULT_COOKIE_PATH] - Path to the cookie file.
150
+ * @returns {Promise<void>} Resolves when cookies are restored.
151
+ */
152
+ function _restoreCookies(page_1) {
153
+ return __awaiter(this, arguments, void 0, function* (page, cookieFilePath = DEFAULT_COOKIE_PATH) {
154
+ const cookies = loadCookies(cookieFilePath);
155
+ if (cookies) {
156
+ yield page.setCookie(...cookies);
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * Writes a question to the ChatGPT prompt textarea, handling multi-line questions.
162
+ *
163
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
164
+ * @param {string} question - The question text to write.
165
+ * @returns {Promise<void>} Resolves when the question is written.
166
+ */
167
+ function writeQuestion(page, question) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ const promptTextarea = yield page.waitForSelector("#prompt-textarea", { timeout: 30000 });
170
+ if (!promptTextarea) {
171
+ console.log("Cannot find the prompt input on the webpage. Please check whether you have access to chat.openai.com without logging in via your browser.");
172
+ return;
173
+ }
174
+ // Inject the full prompt instantly and emit input-like events so the UI reacts.
175
+ yield page.evaluate((text) => {
176
+ const promptEl = document.querySelector("#prompt-textarea");
177
+ if (!promptEl) {
178
+ return;
179
+ }
180
+ promptEl.focus();
181
+ promptEl.innerHTML = "";
182
+ const lines = String(text).split("\n");
183
+ for (const line of lines) {
184
+ const p = document.createElement("p");
185
+ p.textContent = line;
186
+ promptEl.appendChild(p);
187
+ }
188
+ promptEl.dispatchEvent(new InputEvent("beforeinput", { bubbles: true, inputType: "insertFromPaste", data: text }));
189
+ promptEl.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertFromPaste", data: text }));
190
+ promptEl.dispatchEvent(new Event("change", { bubbles: true }));
191
+ }, question);
192
+ // If the app state did not pick up the DOM injection, use keyboard insertion as a reliable fallback.
193
+ const hasPromptText = yield page.evaluate(() => {
194
+ const promptEl = document.querySelector("#prompt-textarea");
195
+ return Boolean(promptEl && promptEl.textContent && promptEl.textContent.trim().length > 0);
196
+ });
197
+ if (!hasPromptText) {
198
+ console.log("Prompt state not updated by DOM injection. Falling back to keyboard insertText.");
199
+ yield promptTextarea.click({ clickCount: 1 });
200
+ yield page.keyboard.down("Control");
201
+ yield page.keyboard.press("KeyA");
202
+ yield page.keyboard.up("Control");
203
+ yield page.keyboard.insertText(question);
204
+ }
205
+ });
206
+ }
207
+ /**
208
+ * Clicks the submit button in ChatGPT interface, trying different button variants.
209
+ *
210
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
211
+ * @returns {Promise<boolean>} Resolves to true when submission is detected, otherwise false.
212
+ */
213
+ function clickSubmitButton(page) {
214
+ return __awaiter(this, void 0, void 0, function* () {
215
+ console.log("Attempting to click the submit button...");
216
+ try {
217
+ const userMessageCountBefore = yield page.$$eval('[data-message-author-role="user"]', (elements) => elements.length);
218
+ const waitForSubmit = (...args_1) => __awaiter(this, [...args_1], void 0, function* (timeout = 5000) {
219
+ try {
220
+ yield page.waitForFunction((previousCount) => {
221
+ const currentCount = document.querySelectorAll('[data-message-author-role="user"]').length;
222
+ return currentCount > previousCount;
223
+ }, { timeout }, userMessageCountBefore);
224
+ return true;
225
+ }
226
+ catch (_a) {
227
+ return false;
228
+ }
229
+ });
230
+ yield page.waitForFunction(() => {
231
+ const candidates = [
232
+ document.querySelector('[data-testid="fruitjuice-send-button"]'),
233
+ document.querySelector('#composer-submit-button'),
234
+ document.querySelector('[data-testid="send-button"]')
235
+ ].filter(Boolean);
236
+ return candidates.some((button) => {
237
+ const isDisabled = button.disabled || button.getAttribute("aria-disabled") === "true";
238
+ const isVisible = button.offsetParent !== null;
239
+ return !isDisabled && isVisible;
240
+ });
241
+ }, { timeout: 5000 }).catch(() => {
242
+ // Continue to diagnostics below even if no enabled button was found within timeout.
243
+ });
244
+ const buttonDetails = yield page.evaluate(() => {
245
+ const selectors = [
246
+ '[data-testid="fruitjuice-send-button"]',
247
+ "#composer-submit-button",
248
+ '[data-testid="send-button"]'
249
+ ];
250
+ const details = selectors.map((selector) => {
251
+ const el = document.querySelector(selector);
252
+ const exists = Boolean(el);
253
+ const disabled = exists
254
+ ? Boolean(el.disabled || el.getAttribute("aria-disabled") === "true")
255
+ : null;
256
+ const visible = exists ? el.offsetParent !== null : null;
257
+ return { selector, exists, disabled, visible };
258
+ });
259
+ return details;
260
+ });
261
+ console.log(`Submit button details: ${JSON.stringify(buttonDetails)}`);
262
+ const clickable = buttonDetails.find((item) => item.exists && item.visible && item.disabled === false);
263
+ const selectedSelector = clickable ? clickable.selector : null;
264
+ if (selectedSelector) {
265
+ yield page.click(selectedSelector);
266
+ console.log(`Clicked submit button selector: ${selectedSelector}`);
267
+ if (yield waitForSubmit(5000)) {
268
+ console.log("Submission detected after selector click.");
269
+ return true;
270
+ }
271
+ // Fallback: force a DOM click in case pointer-interception blocked page.click.
272
+ const forcedClickWorked = yield page.evaluate((selector) => {
273
+ const el = document.querySelector(selector);
274
+ if (!el) {
275
+ return false;
276
+ }
277
+ el.click();
278
+ return true;
279
+ }, selectedSelector);
280
+ if (forcedClickWorked) {
281
+ console.log(`Forced DOM click on selector: ${selectedSelector}`);
282
+ if (yield waitForSubmit(5000)) {
283
+ console.log("Submission detected after forced DOM click.");
284
+ return true;
285
+ }
286
+ }
287
+ }
288
+ console.log("Submit button path did not submit. Trying Enter key fallback on prompt.");
289
+ yield page.focus("#prompt-textarea");
290
+ yield page.keyboard.press("Enter");
291
+ if (yield waitForSubmit(5000)) {
292
+ console.log("Submission detected after Enter key fallback.");
293
+ return true;
294
+ }
295
+ // Final fallback: submit the nearest composer form.
296
+ const didRequestSubmit = yield page.evaluate(() => {
297
+ const prompt = document.querySelector("#prompt-textarea");
298
+ if (!prompt) {
299
+ return false;
300
+ }
301
+ const form = prompt.closest("form");
302
+ if (!form) {
303
+ return false;
304
+ }
305
+ if (typeof form.requestSubmit === "function") {
306
+ form.requestSubmit();
307
+ }
308
+ else {
309
+ form.submit();
310
+ }
311
+ return true;
312
+ });
313
+ if (didRequestSubmit) {
314
+ console.log("Triggered form submit fallback.");
315
+ if (yield waitForSubmit(5000)) {
316
+ console.log("Submission detected after form submit fallback.");
317
+ return true;
318
+ }
319
+ }
320
+ console.log("Failed to submit prompt after all strategies.");
321
+ return false;
322
+ }
323
+ catch (e) {
324
+ console.log(`Failed to click the send button: ${e}`);
325
+ return false;
326
+ }
327
+ });
328
+ }
329
+ let lastMessageId = null;
330
+ let messageCount = 0;
331
+ const is_streaming = false; // Set to true if you want to stream the response
332
+ /**
333
+ * Creates a promise that resolves after a specified number of milliseconds.
334
+ *
335
+ * @param {number} ms - The number of milliseconds to wait.
336
+ * @returns {Promise<void>} A promise that resolves after the specified delay.
337
+ */
338
+ function sleep(ms) {
339
+ return new Promise((resolve) => setTimeout(resolve, ms));
340
+ }
341
+ /**
342
+ * Waits for the initial assistant response to appear and finish thinking.
343
+ *
344
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
345
+ * @param {number} [timeout=30000] - Maximum time to wait for the response (ms).
346
+ * @returns {Promise<void>} Resolves when the initial response is ready.
347
+ */
348
+ function waitForInitialResponse(page_1) {
349
+ return __awaiter(this, arguments, void 0, function* (page, timeout = 30000) {
350
+ const startTime = Date.now();
351
+ while (Date.now() - startTime < timeout) {
352
+ const assistantMessages = yield page.$$('[data-message-author-role="assistant"]');
353
+ const currentMessageCount = assistantMessages.length;
354
+ if (currentMessageCount > messageCount) {
355
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
356
+ const isThinking = yield lastMessage.$(".result-thinking");
357
+ if (!isThinking) {
358
+ lastMessageId = yield page.evaluate((element) => element.getAttribute("data-message-id"), lastMessage);
359
+ messageCount = currentMessageCount;
360
+ return;
361
+ }
362
+ }
363
+ yield sleep(100);
364
+ }
365
+ console.log("Timed out waiting for the initial response.");
366
+ });
367
+ }
368
+ /**
369
+ * Handles streaming response from the assistant, printing output as it arrives.
370
+ *
371
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
372
+ * @param {string} [outputFile] - Path to save the response. Defaults to tmp/response.txt.
373
+ * @returns {Promise<void>} Resolves when streaming is complete.
374
+ */
375
+ function handleStreamingResponse(page_1) {
376
+ return __awaiter(this, arguments, void 0, function* (page, outputFile = upath_1.default.join(process.cwd(), "tmp/response.txt")) {
377
+ let previousText = "";
378
+ let completeResponse = "";
379
+ let newContentDetected = false;
380
+ while (!newContentDetected) {
381
+ const assistantMessages = yield page.$$('[data-message-author-role="assistant"]');
382
+ if (assistantMessages.length > 0) {
383
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
384
+ const currentMessageId = yield page.evaluate((element) => element.getAttribute("data-message-id"), lastMessage);
385
+ if (currentMessageId === lastMessageId) {
386
+ const currentText = yield page.evaluate((element) => element.textContent, lastMessage);
387
+ console.log(`Current text: ${currentText}`);
388
+ if (currentText !== previousText) {
389
+ if (is_streaming) {
390
+ process.stdout.write(currentText.slice(previousText.length));
391
+ }
392
+ else {
393
+ completeResponse += currentText.slice(previousText.length);
394
+ }
395
+ }
396
+ previousText = currentText;
397
+ const isStreaming = yield lastMessage.$(".result-streaming");
398
+ if (!isStreaming) {
399
+ newContentDetected = true;
400
+ }
401
+ }
402
+ else {
403
+ lastMessageId = currentMessageId;
404
+ }
405
+ }
406
+ yield sleep(100);
407
+ }
408
+ if (!is_streaming) {
409
+ console.log(completeResponse.trim());
410
+ console.log("\n\n");
411
+ fs_extra_1.default.ensureDirSync(upath_1.default.dirname(outputFile));
412
+ fs_extra_1.default.writeFileSync(outputFile, completeResponse.trim());
413
+ console.log(`Response saved to ${outputFile}`);
414
+ }
415
+ });
416
+ }
417
+ /**
418
+ * Checks if the user is logged into ChatGPT by checking if login button exists and is visible.
419
+ *
420
+ * @param {import('puppeteer').Page} page - Puppeteer page instance.
421
+ * @returns {Promise<boolean>} True if logged in (no visible login button), false if not logged in.
422
+ */
423
+ function isLoggedIn(page) {
424
+ return __awaiter(this, void 0, void 0, function* () {
425
+ const result = yield page.evaluate(() => {
426
+ const loginButton = document.querySelector('[data-testid="login-button"]');
427
+ // User is NOT logged in if login button exists and is visible
428
+ return !(loginButton && loginButton.offsetParent !== null);
429
+ });
430
+ // Ensure we always return a boolean
431
+ return result === true;
432
+ });
433
+ }
434
+ /**
435
+ * Creates a new Puppeteer browser instance with StealthPlugin enabled.
436
+ *
437
+ * @param {Parameters<import("puppeteer-extra").VanillaPuppeteer["launch"]>[0]} [browserOptions={}] - Browser launch options.
438
+ * @returns {Promise<import("puppeteer-extra").Browser>} The created browser instance.
439
+ */
440
+ function createBrowser() {
441
+ return __awaiter(this, arguments, void 0, function* (browserOptions = {}) {
442
+ const windowsChromeExecutable = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
443
+ const hasWindowsChrome = process.platform === "win32" && fs_extra_1.default.existsSync(windowsChromeExecutable);
444
+ /**
445
+ * @type {Parameters<import("puppeteer-extra").VanillaPuppeteer["launch"]>[0]}
446
+ */
447
+ const defaultOptions = Object.assign({ headless: false, defaultViewport: null, userDataDir: upath_1.default.join(process.cwd(), "tmp/puppeteer-profile"),
448
+ // Windows-specific options to handle browser launch issues
449
+ args: [
450
+ "--start-maximized",
451
+ "--no-sandbox",
452
+ "--disable-setuid-sandbox",
453
+ "--disable-dev-shm-usage",
454
+ "--disable-accelerated-2d-canvas",
455
+ "--no-first-run",
456
+ "--no-zygote",
457
+ "--disable-gpu",
458
+ "--disable-background-timer-throttling",
459
+ "--disable-backgrounding-occluded-windows",
460
+ "--disable-renderer-backgrounding"
461
+ ], ignoreDefaultArgs: ["--disable-extensions"] }, (hasWindowsChrome && {
462
+ // Prefer local Chrome installation when present on Windows.
463
+ executablePath: windowsChromeExecutable
464
+ }));
465
+ try {
466
+ return yield puppeteer_extra_1.default.use((0, puppeteer_extra_plugin_stealth_1.default)()).launch(Object.assign(Object.assign({}, defaultOptions), browserOptions));
467
+ }
468
+ catch (_error) {
469
+ console.error("Failed to launch browser with default options. Trying fallback options...");
470
+ // Fallback: Try with minimal options
471
+ try {
472
+ return yield puppeteer_extra_1.default.use((0, puppeteer_extra_plugin_stealth_1.default)()).launch(Object.assign(Object.assign({ headless: browserOptions.headless || false, defaultViewport: null, args: ["--start-maximized", "--no-sandbox", "--disable-setuid-sandbox"], ignoreDefaultArgs: false }, (hasWindowsChrome && {
473
+ executablePath: windowsChromeExecutable
474
+ })), browserOptions));
475
+ }
476
+ catch (fallbackError) {
477
+ console.error("Browser launch failed completely. Common solutions:");
478
+ console.error("1. Install Google Chrome if not installed");
479
+ console.error("2. Update Node.js to the latest version");
480
+ console.error("3. Try running: npm install puppeteer --force");
481
+ console.error("4. Check if antivirus is blocking browser launch");
482
+ throw new Error(`Browser launch failed: ${fallbackError.message}`);
483
+ }
484
+ }
485
+ });
486
+ }
487
+ /**
488
+ * Handles the login process for ChatGPT by launching a browser and clicking the login button if needed.
489
+ *
490
+ * @returns {Promise<void>} Resolves when the login process is complete.
491
+ */
492
+ function loginToChatGpt() {
493
+ return __awaiter(this, void 0, void 0, function* () {
494
+ const browser = yield createBrowser({ headless: false });
495
+ const page = (yield browser.pages()).length > 0 ? (yield browser.pages())[0] : yield browser.newPage();
496
+ const url = "https://chat.openai.com";
497
+ const navigate = yield navigatePage(page, url);
498
+ // Wait for page to fully load before checking login status
499
+ yield navigate.waitForDomIdle(2000, 10000);
500
+ // Check if the login button exists
501
+ const loginButtonExists = yield page.evaluate(() => {
502
+ return document.querySelector('[data-testid="login-button"]') !== null;
503
+ });
504
+ if (loginButtonExists) {
505
+ console.log("Login button found, clicking to log in...");
506
+ yield page.click('[data-testid="login-button"]');
507
+ // Wait for the login process to complete without requiring full network idleness.
508
+ yield page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: NAVIGATION_TIMEOUT_MS });
509
+ try {
510
+ yield page.waitForNetworkIdle({ idleTime: 1000, timeout: NETWORK_IDLE_TIMEOUT_MS });
511
+ }
512
+ catch (_a) {
513
+ // Ignore: authentication pages can keep background connections active.
514
+ }
515
+ console.log("Login process completed.");
516
+ }
517
+ else {
518
+ console.log("No login required - user appears to be already logged in.");
519
+ }
520
+ });
521
+ }
522
+ /**
523
+ * Automates ChatGPT interactions using Puppeteer. Can send text questions or upload files to ChatGPT.
524
+ *
525
+ * @param {Object} [chatgptOptions={}] - Configuration options for ChatGPT automation.
526
+ * @param {boolean} [chatgptOptions.headless=true] - Whether to run the browser in headless mode.
527
+ * @param {string} [chatgptOptions.question] - Text question to send to ChatGPT. Either question or questionFile must be provided.
528
+ * @param {string} [chatgptOptions.questionFile] - Path to a file to upload to ChatGPT. Either question or questionFile must be provided.
529
+ * @param {string} [chatgptOptions.responseFile] - Path to save the response. Defaults to tmp/response.txt.
530
+ * @returns {Promise<void>} Resolves when the ChatGPT interaction is complete. Responses are logged to console and saved to specified file.
531
+ * @throws {Error} Throws an error if neither question nor questionFile is provided.
532
+ *
533
+ * @example
534
+ * // Send a text question
535
+ * await runChatGpt({
536
+ * headless: false,
537
+ * question: "What is the capital of France?"
538
+ * });
539
+ *
540
+ * @example
541
+ * // Upload a file for analysis
542
+ * await runChatGpt({
543
+ * headless: false,
544
+ * questionFile: "./path/to/document.txt"
545
+ * });
546
+ */
547
+ function runChatGpt() {
548
+ return __awaiter(this, arguments, void 0, function* (chatgptOptions = {}) {
549
+ const headless = chatgptOptions.headless !== undefined ? chatgptOptions.headless : true;
550
+ const questionFile = chatgptOptions.questionFile;
551
+ let question = chatgptOptions.question;
552
+ let shouldUploadQuestionFile = Boolean(questionFile);
553
+ const responseFile = chatgptOptions.responseFile || upath_1.default.join(process.cwd(), "tmp", "response.txt");
554
+ // Validate input parameters
555
+ const noInputProvided = !question && !questionFile;
556
+ const questionIsEmpty = question && question.trim().length === 0;
557
+ const questionFileIsEmpty = questionFile && questionFile.trim().length === 0;
558
+ if (noInputProvided || questionIsEmpty || questionFileIsEmpty) {
559
+ throw new Error("You must provide a question or a question file.");
560
+ }
561
+ // For small files, send content as plain text to avoid file-upload login requirements.
562
+ if (!question && questionFile) {
563
+ if (!fs_extra_1.default.existsSync(questionFile)) {
564
+ throw new Error(`Question file does not exist: ${questionFile}`);
565
+ }
566
+ const questionFileStats = fs_extra_1.default.statSync(questionFile);
567
+ if (questionFileStats.size <= MAX_INLINE_QUESTION_FILE_BYTES) {
568
+ question = fs_extra_1.default.readFileSync(questionFile, "utf8").trim();
569
+ if (!question) {
570
+ throw new Error("Question file is empty.");
571
+ }
572
+ shouldUploadQuestionFile = false;
573
+ console.log(`Question file is ${questionFileStats.size} bytes (<= ${MAX_INLINE_QUESTION_FILE_BYTES}). Sending as text prompt.`);
574
+ }
575
+ }
576
+ let browser;
577
+ try {
578
+ browser = yield createBrowser({ headless });
579
+ }
580
+ catch (error) {
581
+ console.error("Error running ChatGPT:", error);
582
+ console.error("\nTroubleshooting steps:");
583
+ console.error("1. Make sure Google Chrome is installed");
584
+ console.error("2. Try running: yarn add puppeteer --force");
585
+ console.error("3. Check if your antivirus is blocking the browser");
586
+ console.error("4. Close any running Chrome instances and try again");
587
+ throw error;
588
+ }
589
+ const allPages = yield browser.pages();
590
+ /** @type {import('puppeteer').Page} */
591
+ const page = allPages.length > 0 ? allPages[0] : yield browser.newPage();
592
+ yield page.bringToFront();
593
+ // Close other pages if more than one page is open.
594
+ if (allPages.length > 1) {
595
+ for (const p of allPages) {
596
+ if (p !== page) {
597
+ yield p.close();
598
+ }
599
+ }
600
+ }
601
+ try {
602
+ const url = "https://chat.openai.com";
603
+ const navigate = yield navigatePage(page, url);
604
+ // Check temporary chat - wait for page to load and try to click temporary chat button
605
+ yield navigate.waitForDomIdle(2000, 15000);
606
+ try {
607
+ const tempChatButton = yield page.$('button[aria-label="Turn on temporary chat"]');
608
+ if (tempChatButton) {
609
+ yield page.evaluate((el) => el.click(), tempChatButton);
610
+ console.log("Successfully clicked temporary chat button");
611
+ yield navigate.waitForDomIdle(1000, 10000);
612
+ }
613
+ else {
614
+ console.log("Temporary chat button not found, proceeding without it.");
615
+ }
616
+ }
617
+ catch (error) {
618
+ console.log(`Failed to click temporary chat button: ${error.message}`);
619
+ }
620
+ if (question) {
621
+ yield writeQuestion(page, question);
622
+ // Submit the question
623
+ const didSubmit = yield clickSubmitButton(page);
624
+ if (!didSubmit) {
625
+ throw new Error("Prompt was not submitted. The composer button may be disabled or blocked.");
626
+ }
627
+ yield navigate.waitForDomIdle(1000, 30000); // Wait for DOM to stabilize
628
+ // Wait for the initial response
629
+ yield waitForInitialResponse(page);
630
+ // Handle the streaming response
631
+ yield handleStreamingResponse(page, responseFile);
632
+ // Save cookies for this host at the end
633
+ yield saveCookies(page, getCookiePathForUrl(url));
634
+ }
635
+ else if (shouldUploadQuestionFile && questionFile) {
636
+ // Wait for page to fully load before checking login status
637
+ yield navigate.waitForDomIdle(2000, 10000);
638
+ // Check if logged in
639
+ const isUserLoggedIn = yield isLoggedIn(page);
640
+ console.log(`Login status: ${isUserLoggedIn ? "Logged in" : "Not logged in"}`);
641
+ if (!isUserLoggedIn) {
642
+ console.log("Not logged in. Please log in to ChatGPT in the browser window, then close it and run the command again.");
643
+ return loginToChatGpt();
644
+ }
645
+ // Upload the question file
646
+ const plusButtonExists = yield page.evaluate(() => {
647
+ const button = document.querySelector('[data-testid="composer-plus-btn"]');
648
+ return button !== null;
649
+ });
650
+ if (plusButtonExists) {
651
+ yield page.click('[data-testid="composer-plus-btn"]');
652
+ yield sleep(500); // Wait for the menu to open
653
+ const menuItems = yield page.$$('[role="menuitem"]');
654
+ let clicked = false;
655
+ for (const item of menuItems) {
656
+ const text = yield page.evaluate((el) => el.innerText, item);
657
+ if (text && text.includes("Add photos") && text.includes("files")) {
658
+ yield item.hover();
659
+ clicked = true;
660
+ break;
661
+ }
662
+ }
663
+ if (!clicked) {
664
+ console.log('Could not find the "Add photos & files" menu item.');
665
+ return;
666
+ }
667
+ // Wait for file input to appear and upload the file
668
+ try {
669
+ yield sleep(1000); // Wait for file dialog to be ready
670
+ // Look for the file input element
671
+ const fileInput = yield page.waitForSelector('input[type="file"]', { timeout: 10000 });
672
+ if (fileInput) {
673
+ console.log(`Uploading file: ${questionFile}`);
674
+ yield fileInput.uploadFile(questionFile);
675
+ // Wait for the file to be processed
676
+ yield navigate.waitForDomIdle(2000, 15000);
677
+ console.log("File uploaded successfully");
678
+ // Optionally submit after file upload
679
+ const didSubmit = yield clickSubmitButton(page);
680
+ if (!didSubmit) {
681
+ throw new Error("Prompt was not submitted after file upload.");
682
+ }
683
+ yield navigate.waitForDomIdle(1000, 30000);
684
+ // Wait for and handle response
685
+ yield waitForInitialResponse(page);
686
+ yield handleStreamingResponse(page, responseFile);
687
+ }
688
+ else {
689
+ console.log("Could not find file input element");
690
+ }
691
+ }
692
+ catch (error) {
693
+ console.log(`Error uploading file: ${error.message}`);
694
+ }
695
+ }
696
+ else {
697
+ console.log('Could not find the [data-testid="composer-plus-btn"] button.');
698
+ }
699
+ }
700
+ }
701
+ finally {
702
+ // Always close the browser, even if an error occurred
703
+ if (browser) {
704
+ yield browser.close();
705
+ }
706
+ }
707
+ });
708
+ }