@quantiya/codevibe-claude-plugin 1.0.11 → 1.0.13

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 (473) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/server.js +16 -1162
  3. package/package.json +5 -5
  4. package/dist/appsync-client.js +0 -858
  5. package/dist/auth-cli.js +0 -472
  6. package/dist/command-executor.js +0 -127
  7. package/dist/config.js +0 -106
  8. package/dist/crypto-service.js +0 -278
  9. package/dist/http-api.js +0 -334
  10. package/dist/key-manager.js +0 -287
  11. package/dist/logger.js +0 -18
  12. package/dist/prompt-responder.js +0 -132
  13. package/dist/token-storage.js +0 -169
  14. package/dist/types.js +0 -17
  15. package/node_modules/@quantiya/codevibe-core/README.md +0 -170
  16. package/node_modules/@quantiya/codevibe-core/bin/codevibe.js +0 -7
  17. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +0 -132
  18. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.js +0 -576
  19. package/node_modules/@quantiya/codevibe-core/dist/appsync/index.d.ts +0 -2
  20. package/node_modules/@quantiya/codevibe-core/dist/appsync/index.js +0 -10
  21. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +0 -16
  22. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.js +0 -189
  23. package/node_modules/@quantiya/codevibe-core/dist/auth/auth-cli.d.ts +0 -5
  24. package/node_modules/@quantiya/codevibe-core/dist/auth/auth-cli.js +0 -217
  25. package/node_modules/@quantiya/codevibe-core/dist/auth/auth-service.d.ts +0 -87
  26. package/node_modules/@quantiya/codevibe-core/dist/auth/auth-service.js +0 -464
  27. package/node_modules/@quantiya/codevibe-core/dist/auth/fetch-helpers.d.ts +0 -11
  28. package/node_modules/@quantiya/codevibe-core/dist/auth/fetch-helpers.js +0 -165
  29. package/node_modules/@quantiya/codevibe-core/dist/auth/index.d.ts +0 -2
  30. package/node_modules/@quantiya/codevibe-core/dist/auth/index.js +0 -9
  31. package/node_modules/@quantiya/codevibe-core/dist/config/config.d.ts +0 -53
  32. package/node_modules/@quantiya/codevibe-core/dist/config/config.js +0 -123
  33. package/node_modules/@quantiya/codevibe-core/dist/config/index.d.ts +0 -2
  34. package/node_modules/@quantiya/codevibe-core/dist/config/index.js +0 -8
  35. package/node_modules/@quantiya/codevibe-core/dist/crypto/crypto-service.d.ts +0 -118
  36. package/node_modules/@quantiya/codevibe-core/dist/crypto/crypto-service.js +0 -284
  37. package/node_modules/@quantiya/codevibe-core/dist/crypto/index.d.ts +0 -1
  38. package/node_modules/@quantiya/codevibe-core/dist/crypto/index.js +0 -9
  39. package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +0 -14
  40. package/node_modules/@quantiya/codevibe-core/dist/index.js +0 -68
  41. package/node_modules/@quantiya/codevibe-core/dist/keychain/index.d.ts +0 -1
  42. package/node_modules/@quantiya/codevibe-core/dist/keychain/index.js +0 -8
  43. package/node_modules/@quantiya/codevibe-core/dist/keychain/keychain-manager.d.ts +0 -125
  44. package/node_modules/@quantiya/codevibe-core/dist/keychain/keychain-manager.js +0 -375
  45. package/node_modules/@quantiya/codevibe-core/dist/logger/index.d.ts +0 -1
  46. package/node_modules/@quantiya/codevibe-core/dist/logger/index.js +0 -8
  47. package/node_modules/@quantiya/codevibe-core/dist/logger/logger.d.ts +0 -35
  48. package/node_modules/@quantiya/codevibe-core/dist/logger/logger.js +0 -142
  49. package/node_modules/@quantiya/codevibe-core/dist/prompt-parser.d.ts +0 -39
  50. package/node_modules/@quantiya/codevibe-core/dist/prompt-parser.js +0 -236
  51. package/node_modules/@quantiya/codevibe-core/dist/session/index.d.ts +0 -2
  52. package/node_modules/@quantiya/codevibe-core/dist/session/index.js +0 -7
  53. package/node_modules/@quantiya/codevibe-core/dist/session/session-resume.d.ts +0 -55
  54. package/node_modules/@quantiya/codevibe-core/dist/session/session-resume.js +0 -151
  55. package/node_modules/@quantiya/codevibe-core/dist/types/auth.d.ts +0 -15
  56. package/node_modules/@quantiya/codevibe-core/dist/types/auth.js +0 -3
  57. package/node_modules/@quantiya/codevibe-core/dist/types/encryption.d.ts +0 -54
  58. package/node_modules/@quantiya/codevibe-core/dist/types/encryption.js +0 -3
  59. package/node_modules/@quantiya/codevibe-core/dist/types/events.d.ts +0 -74
  60. package/node_modules/@quantiya/codevibe-core/dist/types/events.js +0 -28
  61. package/node_modules/@quantiya/codevibe-core/dist/types/index.d.ts +0 -4
  62. package/node_modules/@quantiya/codevibe-core/dist/types/index.js +0 -22
  63. package/node_modules/@quantiya/codevibe-core/dist/types/session.d.ts +0 -59
  64. package/node_modules/@quantiya/codevibe-core/dist/types/session.js +0 -22
  65. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/CHANGELOG.md +0 -274
  66. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/CONTRIBUTING.md +0 -18
  67. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/LICENSE.md +0 -9
  68. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/README.md +0 -466
  69. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/bin/uuid +0 -2
  70. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/index.js +0 -79
  71. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/md5.js +0 -223
  72. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/native.js +0 -11
  73. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/nil.js +0 -8
  74. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/parse.js +0 -45
  75. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/regex.js +0 -8
  76. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/rng.js +0 -25
  77. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/sha1.js +0 -104
  78. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/stringify.js +0 -44
  79. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/v1.js +0 -107
  80. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/v3.js +0 -16
  81. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/v35.js +0 -80
  82. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/v4.js +0 -43
  83. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/v5.js +0 -16
  84. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/validate.js +0 -17
  85. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/commonjs-browser/version.js +0 -21
  86. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/index.js +0 -9
  87. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/md5.js +0 -215
  88. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/native.js +0 -4
  89. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/nil.js +0 -1
  90. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/parse.js +0 -35
  91. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/regex.js +0 -1
  92. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/rng.js +0 -18
  93. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/sha1.js +0 -96
  94. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/stringify.js +0 -33
  95. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/v1.js +0 -95
  96. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/v3.js +0 -4
  97. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/v35.js +0 -66
  98. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/v4.js +0 -29
  99. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/v5.js +0 -4
  100. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/validate.js +0 -7
  101. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-browser/version.js +0 -11
  102. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/index.js +0 -9
  103. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/md5.js +0 -13
  104. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/native.js +0 -4
  105. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/nil.js +0 -1
  106. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/parse.js +0 -35
  107. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/regex.js +0 -1
  108. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/rng.js +0 -12
  109. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/sha1.js +0 -13
  110. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/stringify.js +0 -33
  111. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/v1.js +0 -95
  112. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/v3.js +0 -4
  113. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/v35.js +0 -66
  114. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/v4.js +0 -29
  115. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/v5.js +0 -4
  116. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/validate.js +0 -7
  117. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/esm-node/version.js +0 -11
  118. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/index.js +0 -79
  119. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/md5-browser.js +0 -223
  120. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/md5.js +0 -23
  121. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/native-browser.js +0 -11
  122. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/native.js +0 -15
  123. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/nil.js +0 -8
  124. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/parse.js +0 -45
  125. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/regex.js +0 -8
  126. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/rng-browser.js +0 -25
  127. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/rng.js +0 -24
  128. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/sha1-browser.js +0 -104
  129. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/sha1.js +0 -23
  130. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/stringify.js +0 -44
  131. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/uuid-bin.js +0 -85
  132. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/v1.js +0 -107
  133. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/v3.js +0 -16
  134. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/v35.js +0 -80
  135. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/v4.js +0 -43
  136. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/v5.js +0 -16
  137. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/validate.js +0 -17
  138. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/dist/version.js +0 -21
  139. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/package.json +0 -135
  140. package/node_modules/@quantiya/codevibe-core/node_modules/uuid/wrapper.mjs +0 -10
  141. package/node_modules/@quantiya/codevibe-core/package.json +0 -51
  142. package/node_modules/base64-js/LICENSE +0 -21
  143. package/node_modules/base64-js/README.md +0 -34
  144. package/node_modules/base64-js/base64js.min.js +0 -1
  145. package/node_modules/base64-js/index.d.ts +0 -3
  146. package/node_modules/base64-js/index.js +0 -150
  147. package/node_modules/base64-js/package.json +0 -47
  148. package/node_modules/bl/.travis.yml +0 -17
  149. package/node_modules/bl/BufferList.js +0 -396
  150. package/node_modules/bl/LICENSE.md +0 -13
  151. package/node_modules/bl/README.md +0 -247
  152. package/node_modules/bl/bl.js +0 -84
  153. package/node_modules/bl/package.json +0 -37
  154. package/node_modules/bl/test/convert.js +0 -21
  155. package/node_modules/bl/test/indexOf.js +0 -492
  156. package/node_modules/bl/test/isBufferList.js +0 -32
  157. package/node_modules/bl/test/test.js +0 -869
  158. package/node_modules/buffer/AUTHORS.md +0 -70
  159. package/node_modules/buffer/LICENSE +0 -21
  160. package/node_modules/buffer/README.md +0 -410
  161. package/node_modules/buffer/index.d.ts +0 -186
  162. package/node_modules/buffer/index.js +0 -1817
  163. package/node_modules/buffer/package.json +0 -96
  164. package/node_modules/chownr/LICENSE +0 -15
  165. package/node_modules/chownr/README.md +0 -3
  166. package/node_modules/chownr/chownr.js +0 -167
  167. package/node_modules/chownr/package.json +0 -29
  168. package/node_modules/decompress-response/index.d.ts +0 -22
  169. package/node_modules/decompress-response/index.js +0 -58
  170. package/node_modules/decompress-response/license +0 -9
  171. package/node_modules/decompress-response/package.json +0 -56
  172. package/node_modules/decompress-response/readme.md +0 -48
  173. package/node_modules/deep-extend/CHANGELOG.md +0 -46
  174. package/node_modules/deep-extend/LICENSE +0 -20
  175. package/node_modules/deep-extend/README.md +0 -91
  176. package/node_modules/deep-extend/index.js +0 -1
  177. package/node_modules/deep-extend/lib/deep-extend.js +0 -150
  178. package/node_modules/deep-extend/package.json +0 -62
  179. package/node_modules/detect-libc/LICENSE +0 -201
  180. package/node_modules/detect-libc/README.md +0 -163
  181. package/node_modules/detect-libc/index.d.ts +0 -14
  182. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  183. package/node_modules/detect-libc/lib/elf.js +0 -39
  184. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  185. package/node_modules/detect-libc/lib/process.js +0 -24
  186. package/node_modules/detect-libc/package.json +0 -44
  187. package/node_modules/end-of-stream/LICENSE +0 -21
  188. package/node_modules/end-of-stream/README.md +0 -54
  189. package/node_modules/end-of-stream/index.js +0 -96
  190. package/node_modules/end-of-stream/package.json +0 -37
  191. package/node_modules/expand-template/.travis.yml +0 -6
  192. package/node_modules/expand-template/LICENSE +0 -21
  193. package/node_modules/expand-template/README.md +0 -43
  194. package/node_modules/expand-template/index.js +0 -26
  195. package/node_modules/expand-template/package.json +0 -29
  196. package/node_modules/expand-template/test.js +0 -67
  197. package/node_modules/fs-constants/LICENSE +0 -21
  198. package/node_modules/fs-constants/README.md +0 -26
  199. package/node_modules/fs-constants/browser.js +0 -1
  200. package/node_modules/fs-constants/index.js +0 -1
  201. package/node_modules/fs-constants/package.json +0 -19
  202. package/node_modules/github-from-package/.travis.yml +0 -4
  203. package/node_modules/github-from-package/LICENSE +0 -18
  204. package/node_modules/github-from-package/example/package.json +0 -8
  205. package/node_modules/github-from-package/example/url.js +0 -3
  206. package/node_modules/github-from-package/index.js +0 -17
  207. package/node_modules/github-from-package/package.json +0 -30
  208. package/node_modules/github-from-package/readme.markdown +0 -53
  209. package/node_modules/github-from-package/test/a.json +0 -8
  210. package/node_modules/github-from-package/test/b.json +0 -5
  211. package/node_modules/github-from-package/test/c.json +0 -5
  212. package/node_modules/github-from-package/test/d.json +0 -7
  213. package/node_modules/github-from-package/test/e.json +0 -5
  214. package/node_modules/github-from-package/test/url.js +0 -19
  215. package/node_modules/ieee754/LICENSE +0 -11
  216. package/node_modules/ieee754/README.md +0 -51
  217. package/node_modules/ieee754/index.d.ts +0 -10
  218. package/node_modules/ieee754/index.js +0 -85
  219. package/node_modules/ieee754/package.json +0 -52
  220. package/node_modules/ini/LICENSE +0 -15
  221. package/node_modules/ini/README.md +0 -102
  222. package/node_modules/ini/ini.js +0 -206
  223. package/node_modules/ini/package.json +0 -33
  224. package/node_modules/keytar/LICENSE.md +0 -20
  225. package/node_modules/keytar/README.md +0 -94
  226. package/node_modules/keytar/binding.gyp +0 -66
  227. package/node_modules/keytar/build/Release/keytar.node +0 -0
  228. package/node_modules/keytar/keytar.d.ts +0 -51
  229. package/node_modules/keytar/lib/keytar.js +0 -43
  230. package/node_modules/keytar/package.json +0 -66
  231. package/node_modules/keytar/src/async.cc +0 -242
  232. package/node_modules/keytar/src/async.h +0 -103
  233. package/node_modules/keytar/src/credentials.h +0 -13
  234. package/node_modules/keytar/src/keytar.h +0 -41
  235. package/node_modules/keytar/src/keytar_mac.cc +0 -296
  236. package/node_modules/keytar/src/keytar_posix.cc +0 -184
  237. package/node_modules/keytar/src/keytar_win.cc +0 -272
  238. package/node_modules/keytar/src/main.cc +0 -139
  239. package/node_modules/mimic-response/index.d.ts +0 -17
  240. package/node_modules/mimic-response/index.js +0 -77
  241. package/node_modules/mimic-response/license +0 -9
  242. package/node_modules/mimic-response/package.json +0 -42
  243. package/node_modules/mimic-response/readme.md +0 -78
  244. package/node_modules/minimist/.eslintrc +0 -29
  245. package/node_modules/minimist/.github/FUNDING.yml +0 -12
  246. package/node_modules/minimist/.nycrc +0 -14
  247. package/node_modules/minimist/CHANGELOG.md +0 -298
  248. package/node_modules/minimist/LICENSE +0 -18
  249. package/node_modules/minimist/README.md +0 -121
  250. package/node_modules/minimist/example/parse.js +0 -4
  251. package/node_modules/minimist/index.js +0 -263
  252. package/node_modules/minimist/package.json +0 -75
  253. package/node_modules/minimist/test/all_bool.js +0 -34
  254. package/node_modules/minimist/test/bool.js +0 -177
  255. package/node_modules/minimist/test/dash.js +0 -43
  256. package/node_modules/minimist/test/default_bool.js +0 -37
  257. package/node_modules/minimist/test/dotted.js +0 -24
  258. package/node_modules/minimist/test/kv_short.js +0 -32
  259. package/node_modules/minimist/test/long.js +0 -33
  260. package/node_modules/minimist/test/num.js +0 -38
  261. package/node_modules/minimist/test/parse.js +0 -209
  262. package/node_modules/minimist/test/parse_modified.js +0 -11
  263. package/node_modules/minimist/test/proto.js +0 -64
  264. package/node_modules/minimist/test/short.js +0 -69
  265. package/node_modules/minimist/test/stop_early.js +0 -17
  266. package/node_modules/minimist/test/unknown.js +0 -104
  267. package/node_modules/minimist/test/whitespace.js +0 -10
  268. package/node_modules/mkdirp-classic/LICENSE +0 -21
  269. package/node_modules/mkdirp-classic/README.md +0 -18
  270. package/node_modules/mkdirp-classic/index.js +0 -98
  271. package/node_modules/mkdirp-classic/package.json +0 -18
  272. package/node_modules/napi-build-utils/.github/workflows/run-npm-tests.yml +0 -31
  273. package/node_modules/napi-build-utils/LICENSE +0 -21
  274. package/node_modules/napi-build-utils/README.md +0 -52
  275. package/node_modules/napi-build-utils/index.js +0 -214
  276. package/node_modules/napi-build-utils/index.md +0 -0
  277. package/node_modules/napi-build-utils/package.json +0 -42
  278. package/node_modules/node-abi/LICENSE +0 -21
  279. package/node_modules/node-abi/README.md +0 -54
  280. package/node_modules/node-abi/abi_registry.json +0 -432
  281. package/node_modules/node-abi/index.js +0 -179
  282. package/node_modules/node-abi/package.json +0 -45
  283. package/node_modules/node-addon-api/LICENSE.md +0 -13
  284. package/node_modules/node-addon-api/README.md +0 -293
  285. package/node_modules/node-addon-api/common.gypi +0 -21
  286. package/node_modules/node-addon-api/except.gypi +0 -25
  287. package/node_modules/node-addon-api/index.js +0 -11
  288. package/node_modules/node-addon-api/napi-inl.deprecated.h +0 -192
  289. package/node_modules/node-addon-api/napi-inl.h +0 -6209
  290. package/node_modules/node-addon-api/napi.h +0 -2983
  291. package/node_modules/node-addon-api/node_api.gyp +0 -9
  292. package/node_modules/node-addon-api/noexcept.gypi +0 -26
  293. package/node_modules/node-addon-api/nothing.c +0 -0
  294. package/node_modules/node-addon-api/package-support.json +0 -21
  295. package/node_modules/node-addon-api/package.json +0 -399
  296. package/node_modules/node-addon-api/tools/README.md +0 -73
  297. package/node_modules/node-addon-api/tools/check-napi.js +0 -100
  298. package/node_modules/node-addon-api/tools/clang-format.js +0 -68
  299. package/node_modules/node-addon-api/tools/conversion.js +0 -309
  300. package/node_modules/node-addon-api/tools/eslint-format.js +0 -71
  301. package/node_modules/prebuild-install/CHANGELOG.md +0 -131
  302. package/node_modules/prebuild-install/CONTRIBUTING.md +0 -6
  303. package/node_modules/prebuild-install/LICENSE +0 -21
  304. package/node_modules/prebuild-install/README.md +0 -163
  305. package/node_modules/prebuild-install/asset.js +0 -44
  306. package/node_modules/prebuild-install/bin.js +0 -78
  307. package/node_modules/prebuild-install/download.js +0 -142
  308. package/node_modules/prebuild-install/error.js +0 -14
  309. package/node_modules/prebuild-install/help.txt +0 -16
  310. package/node_modules/prebuild-install/index.js +0 -1
  311. package/node_modules/prebuild-install/log.js +0 -33
  312. package/node_modules/prebuild-install/package.json +0 -67
  313. package/node_modules/prebuild-install/proxy.js +0 -35
  314. package/node_modules/prebuild-install/rc.js +0 -64
  315. package/node_modules/prebuild-install/util.js +0 -143
  316. package/node_modules/pump/.github/FUNDING.yml +0 -2
  317. package/node_modules/pump/.travis.yml +0 -5
  318. package/node_modules/pump/LICENSE +0 -21
  319. package/node_modules/pump/README.md +0 -74
  320. package/node_modules/pump/SECURITY.md +0 -5
  321. package/node_modules/pump/empty.js +0 -1
  322. package/node_modules/pump/index.js +0 -86
  323. package/node_modules/pump/package.json +0 -30
  324. package/node_modules/pump/test-browser.js +0 -66
  325. package/node_modules/pump/test-node.js +0 -53
  326. package/node_modules/rc/LICENSE.APACHE2 +0 -15
  327. package/node_modules/rc/LICENSE.BSD +0 -26
  328. package/node_modules/rc/LICENSE.MIT +0 -24
  329. package/node_modules/rc/README.md +0 -227
  330. package/node_modules/rc/browser.js +0 -7
  331. package/node_modules/rc/cli.js +0 -4
  332. package/node_modules/rc/index.js +0 -53
  333. package/node_modules/rc/lib/utils.js +0 -104
  334. package/node_modules/rc/package.json +0 -29
  335. package/node_modules/rc/test/ini.js +0 -16
  336. package/node_modules/rc/test/nested-env-vars.js +0 -50
  337. package/node_modules/rc/test/test.js +0 -59
  338. package/node_modules/readable-stream/CONTRIBUTING.md +0 -38
  339. package/node_modules/readable-stream/GOVERNANCE.md +0 -136
  340. package/node_modules/readable-stream/LICENSE +0 -47
  341. package/node_modules/readable-stream/README.md +0 -106
  342. package/node_modules/readable-stream/errors-browser.js +0 -127
  343. package/node_modules/readable-stream/errors.js +0 -116
  344. package/node_modules/readable-stream/experimentalWarning.js +0 -17
  345. package/node_modules/readable-stream/lib/_stream_duplex.js +0 -126
  346. package/node_modules/readable-stream/lib/_stream_passthrough.js +0 -37
  347. package/node_modules/readable-stream/lib/_stream_readable.js +0 -1027
  348. package/node_modules/readable-stream/lib/_stream_transform.js +0 -190
  349. package/node_modules/readable-stream/lib/_stream_writable.js +0 -641
  350. package/node_modules/readable-stream/lib/internal/streams/async_iterator.js +0 -180
  351. package/node_modules/readable-stream/lib/internal/streams/buffer_list.js +0 -183
  352. package/node_modules/readable-stream/lib/internal/streams/destroy.js +0 -96
  353. package/node_modules/readable-stream/lib/internal/streams/end-of-stream.js +0 -86
  354. package/node_modules/readable-stream/lib/internal/streams/from-browser.js +0 -3
  355. package/node_modules/readable-stream/lib/internal/streams/from.js +0 -52
  356. package/node_modules/readable-stream/lib/internal/streams/pipeline.js +0 -86
  357. package/node_modules/readable-stream/lib/internal/streams/state.js +0 -22
  358. package/node_modules/readable-stream/lib/internal/streams/stream-browser.js +0 -1
  359. package/node_modules/readable-stream/lib/internal/streams/stream.js +0 -1
  360. package/node_modules/readable-stream/package.json +0 -68
  361. package/node_modules/readable-stream/readable-browser.js +0 -9
  362. package/node_modules/readable-stream/readable.js +0 -16
  363. package/node_modules/safe-buffer/LICENSE +0 -21
  364. package/node_modules/safe-buffer/README.md +0 -584
  365. package/node_modules/safe-buffer/index.d.ts +0 -187
  366. package/node_modules/safe-buffer/index.js +0 -65
  367. package/node_modules/safe-buffer/package.json +0 -51
  368. package/node_modules/semver/LICENSE +0 -15
  369. package/node_modules/semver/README.md +0 -665
  370. package/node_modules/semver/bin/semver.js +0 -191
  371. package/node_modules/semver/classes/comparator.js +0 -143
  372. package/node_modules/semver/classes/index.js +0 -7
  373. package/node_modules/semver/classes/range.js +0 -557
  374. package/node_modules/semver/classes/semver.js +0 -333
  375. package/node_modules/semver/functions/clean.js +0 -8
  376. package/node_modules/semver/functions/cmp.js +0 -54
  377. package/node_modules/semver/functions/coerce.js +0 -62
  378. package/node_modules/semver/functions/compare-build.js +0 -9
  379. package/node_modules/semver/functions/compare-loose.js +0 -5
  380. package/node_modules/semver/functions/compare.js +0 -7
  381. package/node_modules/semver/functions/diff.js +0 -60
  382. package/node_modules/semver/functions/eq.js +0 -5
  383. package/node_modules/semver/functions/gt.js +0 -5
  384. package/node_modules/semver/functions/gte.js +0 -5
  385. package/node_modules/semver/functions/inc.js +0 -21
  386. package/node_modules/semver/functions/lt.js +0 -5
  387. package/node_modules/semver/functions/lte.js +0 -5
  388. package/node_modules/semver/functions/major.js +0 -5
  389. package/node_modules/semver/functions/minor.js +0 -5
  390. package/node_modules/semver/functions/neq.js +0 -5
  391. package/node_modules/semver/functions/parse.js +0 -18
  392. package/node_modules/semver/functions/patch.js +0 -5
  393. package/node_modules/semver/functions/prerelease.js +0 -8
  394. package/node_modules/semver/functions/rcompare.js +0 -5
  395. package/node_modules/semver/functions/rsort.js +0 -5
  396. package/node_modules/semver/functions/satisfies.js +0 -12
  397. package/node_modules/semver/functions/sort.js +0 -5
  398. package/node_modules/semver/functions/valid.js +0 -8
  399. package/node_modules/semver/index.js +0 -91
  400. package/node_modules/semver/internal/constants.js +0 -37
  401. package/node_modules/semver/internal/debug.js +0 -11
  402. package/node_modules/semver/internal/identifiers.js +0 -29
  403. package/node_modules/semver/internal/lrucache.js +0 -42
  404. package/node_modules/semver/internal/parse-options.js +0 -17
  405. package/node_modules/semver/internal/re.js +0 -223
  406. package/node_modules/semver/package.json +0 -78
  407. package/node_modules/semver/preload.js +0 -4
  408. package/node_modules/semver/range.bnf +0 -16
  409. package/node_modules/semver/ranges/gtr.js +0 -6
  410. package/node_modules/semver/ranges/intersects.js +0 -9
  411. package/node_modules/semver/ranges/ltr.js +0 -6
  412. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  413. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  414. package/node_modules/semver/ranges/min-version.js +0 -63
  415. package/node_modules/semver/ranges/outside.js +0 -82
  416. package/node_modules/semver/ranges/simplify.js +0 -49
  417. package/node_modules/semver/ranges/subset.js +0 -249
  418. package/node_modules/semver/ranges/to-comparators.js +0 -10
  419. package/node_modules/semver/ranges/valid.js +0 -13
  420. package/node_modules/simple-concat/.travis.yml +0 -3
  421. package/node_modules/simple-concat/LICENSE +0 -20
  422. package/node_modules/simple-concat/README.md +0 -44
  423. package/node_modules/simple-concat/index.js +0 -15
  424. package/node_modules/simple-concat/package.json +0 -47
  425. package/node_modules/simple-concat/test/basic.js +0 -41
  426. package/node_modules/simple-get/.github/dependabot.yml +0 -15
  427. package/node_modules/simple-get/.github/workflows/ci.yml +0 -23
  428. package/node_modules/simple-get/LICENSE +0 -20
  429. package/node_modules/simple-get/README.md +0 -333
  430. package/node_modules/simple-get/index.js +0 -108
  431. package/node_modules/simple-get/package.json +0 -67
  432. package/node_modules/string_decoder/LICENSE +0 -48
  433. package/node_modules/string_decoder/README.md +0 -47
  434. package/node_modules/string_decoder/lib/string_decoder.js +0 -296
  435. package/node_modules/string_decoder/package.json +0 -34
  436. package/node_modules/strip-json-comments/index.js +0 -70
  437. package/node_modules/strip-json-comments/license +0 -21
  438. package/node_modules/strip-json-comments/package.json +0 -42
  439. package/node_modules/strip-json-comments/readme.md +0 -64
  440. package/node_modules/tar-fs/.travis.yml +0 -6
  441. package/node_modules/tar-fs/LICENSE +0 -21
  442. package/node_modules/tar-fs/README.md +0 -165
  443. package/node_modules/tar-fs/index.js +0 -363
  444. package/node_modules/tar-fs/package.json +0 -41
  445. package/node_modules/tar-fs/test/fixtures/a/hello.txt +0 -1
  446. package/node_modules/tar-fs/test/fixtures/b/a/test.txt +0 -1
  447. package/node_modules/tar-fs/test/fixtures/d/file1 +0 -0
  448. package/node_modules/tar-fs/test/fixtures/d/file2 +0 -0
  449. package/node_modules/tar-fs/test/fixtures/d/sub-dir/file5 +0 -0
  450. package/node_modules/tar-fs/test/fixtures/d/sub-files/file3 +0 -0
  451. package/node_modules/tar-fs/test/fixtures/d/sub-files/file4 +0 -0
  452. package/node_modules/tar-fs/test/fixtures/e/directory/.ignore +0 -0
  453. package/node_modules/tar-fs/test/fixtures/e/file +0 -0
  454. package/node_modules/tar-fs/test/fixtures/invalid.tar +0 -0
  455. package/node_modules/tar-fs/test/index.js +0 -346
  456. package/node_modules/tar-stream/LICENSE +0 -21
  457. package/node_modules/tar-stream/README.md +0 -168
  458. package/node_modules/tar-stream/extract.js +0 -257
  459. package/node_modules/tar-stream/headers.js +0 -295
  460. package/node_modules/tar-stream/index.js +0 -2
  461. package/node_modules/tar-stream/pack.js +0 -255
  462. package/node_modules/tar-stream/package.json +0 -58
  463. package/node_modules/tar-stream/sandbox.js +0 -11
  464. package/node_modules/tunnel-agent/LICENSE +0 -55
  465. package/node_modules/tunnel-agent/README.md +0 -4
  466. package/node_modules/tunnel-agent/index.js +0 -244
  467. package/node_modules/tunnel-agent/package.json +0 -22
  468. package/node_modules/util-deprecate/History.md +0 -16
  469. package/node_modules/util-deprecate/LICENSE +0 -24
  470. package/node_modules/util-deprecate/README.md +0 -53
  471. package/node_modules/util-deprecate/browser.js +0 -67
  472. package/node_modules/util-deprecate/node.js +0 -6
  473. package/node_modules/util-deprecate/package.json +0 -27
package/dist/server.js CHANGED
@@ -1,1162 +1,16 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.parseInteractivePromptInput = parseInteractivePromptInput;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- const logger_1 = require("./logger");
41
- // Import shared modules from codevibe-core
42
- const codevibe_core_1 = require("@quantiya/codevibe-core");
43
- // Import plugin-specific modules
44
- const http_api_1 = require("./http-api");
45
- const command_executor_1 = require("./command-executor");
46
- const prompt_responder_1 = require("./prompt-responder");
47
- class McpServer {
48
- constructor(sessionId) {
49
- this.activeSessions = new Map();
50
- this.assignedPort = 0;
51
- this.sessionKey = null; // E2E encryption session key
52
- // Map Claude Code session IDs to backend session IDs (claude-{uuid} format)
53
- this.claudeToBackendSessionId = new Map();
54
- // Track prompts sent from mobile to avoid duplicate USER_PROMPT events
55
- // When mobile sends a prompt, it gets typed into terminal which triggers UserPromptSubmit hook
56
- // We track with timestamp and expire after 3 seconds to avoid false positives
57
- this.pendingMobilePrompts = new Map();
58
- this.httpApi = new http_api_1.HttpApi();
59
- // AppSyncClient is created in start() after config is loaded with correct environment
60
- this.commandExecutor = new command_executor_1.CommandExecutor();
61
- this.promptResponder = new prompt_responder_1.PromptResponder();
62
- this.initialSessionId = sessionId;
63
- }
64
- /**
65
- * Get the port the server is listening on
66
- */
67
- getPort() {
68
- return this.assignedPort;
69
- }
70
- /**
71
- * Generate backend session ID from Claude Code session ID.
72
- * Format: claude-{claudeSessionId}
73
- */
74
- generateBackendSessionId(claudeSessionId) {
75
- return `claude-${claudeSessionId}`;
76
- }
77
- /**
78
- * Track a mobile prompt to filter out the duplicate USER_PROMPT from desktop hook
79
- */
80
- trackMobilePrompt(sessionId, prompt) {
81
- if (!this.pendingMobilePrompts.has(sessionId)) {
82
- this.pendingMobilePrompts.set(sessionId, []);
83
- }
84
- this.pendingMobilePrompts.get(sessionId).push({
85
- prompt: prompt.trim(),
86
- timestamp: Date.now(),
87
- });
88
- logger_1.logger.debug('Tracking mobile prompt for deduplication', { sessionId, promptLength: prompt.length });
89
- }
90
- /**
91
- * Check if a prompt was recently sent from mobile (within expiry window)
92
- * Returns true and removes the entry if found
93
- */
94
- isRecentMobilePrompt(sessionId, prompt) {
95
- const prompts = this.pendingMobilePrompts.get(sessionId);
96
- if (!prompts)
97
- return false;
98
- const now = Date.now();
99
- const trimmedPrompt = prompt.trim();
100
- // Clean up expired entries and check for match
101
- const validPrompts = [];
102
- let found = false;
103
- for (const entry of prompts) {
104
- if (now - entry.timestamp > McpServer.MOBILE_PROMPT_EXPIRY_MS) {
105
- // Expired, skip
106
- continue;
107
- }
108
- if (!found && entry.prompt === trimmedPrompt) {
109
- // Found match, mark as found but don't add to validPrompts (consume it)
110
- found = true;
111
- logger_1.logger.debug('Found matching mobile prompt, filtering duplicate', { sessionId });
112
- continue;
113
- }
114
- validPrompts.push(entry);
115
- }
116
- // Update the list with remaining valid prompts
117
- if (validPrompts.length > 0) {
118
- this.pendingMobilePrompts.set(sessionId, validPrompts);
119
- }
120
- else {
121
- this.pendingMobilePrompts.delete(sessionId);
122
- }
123
- return found;
124
- }
125
- /**
126
- * Write port file for a session so hooks can discover the server port
127
- */
128
- writePortFile(sessionId) {
129
- const portFilePath = path.join(os.tmpdir(), `codevibe-claude-${sessionId}.port`);
130
- try {
131
- fs.writeFileSync(portFilePath, this.assignedPort.toString());
132
- logger_1.logger.info(`Port file written: ${portFilePath} -> ${this.assignedPort}`);
133
- }
134
- catch (error) {
135
- logger_1.logger.error(`Failed to write port file: ${portFilePath}`, error);
136
- }
137
- }
138
- /**
139
- * Remove port file for a session
140
- */
141
- removePortFile(sessionId) {
142
- const portFilePath = path.join(os.tmpdir(), `codevibe-claude-${sessionId}.port`);
143
- try {
144
- if (fs.existsSync(portFilePath)) {
145
- fs.unlinkSync(portFilePath);
146
- logger_1.logger.info(`Port file removed: ${portFilePath}`);
147
- }
148
- }
149
- catch (error) {
150
- logger_1.logger.warn(`Failed to remove port file: ${portFilePath}`, error);
151
- }
152
- }
153
- async start() {
154
- try {
155
- logger_1.logger.info('Starting CodeVibe MCP Server...', {
156
- environment: (0, codevibe_core_1.getEnvironment)(),
157
- });
158
- // Create AppSyncClient (auto-configures from ENVIRONMENT env var)
159
- this.appSyncClient = new codevibe_core_1.AppSyncClient();
160
- // Authenticate using stored OAuth tokens (from 'codevibe-claude login')
161
- const storedTokensAuth = await this.appSyncClient.authenticateWithStoredTokens();
162
- if (storedTokensAuth) {
163
- logger_1.logger.info('Authenticated with stored OAuth tokens', {
164
- userId: this.appSyncClient.getCurrentUserId(),
165
- email: this.appSyncClient.getCurrentUserEmail(),
166
- });
167
- // Register device encryption key for E2E encryption
168
- await this.registerDeviceEncryptionKey();
169
- }
170
- else {
171
- logger_1.logger.error('Authentication failed. Run "codevibe-claude login" first.');
172
- console.error('Not authenticated. Run "codevibe-claude login" to sign in.');
173
- process.exit(1);
174
- }
175
- // Register event handlers
176
- this.httpApi.onEvent(this.handleEventFromHook.bind(this));
177
- // Start HTTP API with dynamic port allocation
178
- // Pass session ID if provided at startup
179
- this.assignedPort = await this.httpApi.start(this.initialSessionId);
180
- logger_1.logger.info('MCP Server started successfully', {
181
- port: this.assignedPort,
182
- host: (0, codevibe_core_1.getConfig)().server.host,
183
- dynamicPort: (0, codevibe_core_1.getConfig)().server.dynamicPort,
184
- sessionId: this.initialSessionId,
185
- authenticated: this.appSyncClient.isAuthenticated(),
186
- userId: this.appSyncClient.getCurrentUserId(),
187
- });
188
- }
189
- catch (error) {
190
- logger_1.logger.error('Failed to start MCP Server:', error);
191
- throw error;
192
- }
193
- }
194
- async stop() {
195
- logger_1.logger.info('Stopping MCP Server...');
196
- // Mark all active sessions as INACTIVE before shutting down
197
- const sessionIds = Array.from(this.activeSessions.keys());
198
- logger_1.logger.info(`Marking ${sessionIds.length} active session(s) as INACTIVE...`);
199
- for (const sessionId of sessionIds) {
200
- try {
201
- await this.appSyncClient.updateSession({
202
- sessionId,
203
- status: codevibe_core_1.SessionStatus.INACTIVE,
204
- });
205
- logger_1.logger.info('Session marked as INACTIVE during shutdown', { sessionId });
206
- // Remove port file using raw Claude session ID
207
- const state = this.activeSessions.get(sessionId);
208
- if (state) {
209
- this.removePortFile(state.claudeSessionId);
210
- }
211
- }
212
- catch (error) {
213
- logger_1.logger.warn('Failed to mark session as INACTIVE during shutdown', { sessionId, error });
214
- }
215
- }
216
- // Cleanup subscriptions
217
- this.appSyncClient.cleanupSubscriptions();
218
- // Clear active sessions
219
- this.activeSessions.clear();
220
- // Stop HTTP API
221
- await this.httpApi.stop();
222
- logger_1.logger.info('MCP Server stopped');
223
- }
224
- /**
225
- * Handle events received from hook scripts via HTTP API
226
- */
227
- async handleEventFromHook(payload) {
228
- const { session_id, hook_event_name, type, content } = payload;
229
- logger_1.logger.info('Processing hook event', {
230
- sessionId: session_id,
231
- hookEvent: hook_event_name,
232
- type,
233
- });
234
- try {
235
- // Handle session lifecycle events
236
- if (hook_event_name === 'SessionStart') {
237
- await this.handleSessionStart(payload);
238
- }
239
- else if (hook_event_name === 'SessionEnd') {
240
- await this.handleSessionEnd(payload);
241
- }
242
- // Resolve backend session ID (claude-{uuid} format)
243
- const backendSessionId = this.claudeToBackendSessionId.get(session_id)
244
- || this.generateBackendSessionId(session_id);
245
- // Skip USER_PROMPT events that originated from mobile
246
- // When mobile sends a prompt via tmux, it triggers UserPromptSubmit hook
247
- // This creates a duplicate since mobile already created the event
248
- if (type === codevibe_core_1.EventType.USER_PROMPT &&
249
- payload.source === codevibe_core_1.EventSource.DESKTOP &&
250
- hook_event_name === 'UserPromptSubmit' &&
251
- content &&
252
- this.isRecentMobilePrompt(backendSessionId, content)) {
253
- logger_1.logger.info('Skipping duplicate USER_PROMPT from mobile-originated prompt', {
254
- sessionId: backendSessionId,
255
- contentLength: content.length,
256
- });
257
- return; // Don't send to AppSync
258
- }
259
- // Intercept INTERACTIVE_PROMPT — handle async with tmux snapshot parsing
260
- if (type === codevibe_core_1.EventType.INTERACTIVE_PROMPT) {
261
- const sessionState = this.activeSessions.get(backendSessionId);
262
- if (sessionState) {
263
- sessionState.waitingForPromptResponse = true;
264
- sessionState.pendingPromptId = payload.prompt_id;
265
- logger_1.logger.info('Interactive prompt detected - will parse options from tmux', {
266
- sessionId: backendSessionId,
267
- promptId: payload.prompt_id,
268
- });
269
- }
270
- // Fire async — capture tmux after prompt renders, then send to AppSync
271
- this.sendInteractivePromptAsync(backendSessionId, payload, content).catch(e => {
272
- logger_1.logger.error('Failed to send interactive prompt with dynamic options', { error: e });
273
- });
274
- return; // Don't create AppSync event here — async handler will do it
275
- }
276
- // Encrypt event content if we have a session key
277
- let eventContent = content;
278
- let eventMetadata = payload.metadata;
279
- let isEncrypted = false;
280
- // DEBUG: Log session key state for hook events
281
- logger_1.logger.info('Hook event encryption state', {
282
- type,
283
- sessionId: backendSessionId,
284
- hasSessionKey: !!this.sessionKey,
285
- sessionKeyLength: this.sessionKey?.length || 0,
286
- });
287
- if (this.sessionKey) {
288
- eventContent = codevibe_core_1.cryptoService.encryptContent(content, this.sessionKey);
289
- if (eventMetadata) {
290
- const encryptedMeta = codevibe_core_1.cryptoService.encryptMetadata(eventMetadata, this.sessionKey);
291
- eventMetadata = { encrypted: encryptedMeta };
292
- }
293
- isEncrypted = true;
294
- logger_1.logger.info('Event encrypted for hook', { type, sessionId: backendSessionId, isEncrypted: true });
295
- }
296
- else {
297
- logger_1.logger.warn('No session key - event will NOT be encrypted', { type, sessionId: backendSessionId });
298
- }
299
- // Send event to AppSync
300
- const event = await this.appSyncClient.createEvent({
301
- sessionId: backendSessionId,
302
- type,
303
- source: payload.source,
304
- content: eventContent,
305
- metadata: eventMetadata,
306
- promptId: payload.prompt_id,
307
- isEncrypted: isEncrypted ? true : undefined,
308
- });
309
- // Clear waiting state when user sends new prompt from desktop
310
- // (means they answered the prompt locally or moved on)
311
- if (type === codevibe_core_1.EventType.USER_PROMPT && payload.source === codevibe_core_1.EventSource.DESKTOP) {
312
- const sessionState = this.activeSessions.get(backendSessionId);
313
- if (sessionState?.waitingForPromptResponse) {
314
- sessionState.waitingForPromptResponse = false;
315
- sessionState.pendingPromptId = undefined;
316
- sessionState.pendingSubmitMap = undefined;
317
- logger_1.logger.info('Clearing prompt wait state - new desktop prompt received', {
318
- sessionId: backendSessionId,
319
- });
320
- }
321
- }
322
- logger_1.logger.debug('Event sent to AppSync successfully');
323
- }
324
- catch (error) {
325
- logger_1.logger.error('Failed to process hook event:', error);
326
- throw error;
327
- }
328
- }
329
- /**
330
- * Handle SessionStart hook event
331
- *
332
- * Handles both new sessions and /resume:
333
- * - If session exists in backend → reactivate it (same session ID)
334
- * - If session doesn't exist → create new one with that session ID
335
- */
336
- async handleSessionStart(payload) {
337
- const claudeSessionId = payload.session_id;
338
- const sessionId = this.generateBackendSessionId(claudeSessionId);
339
- const cwd = payload.metadata?.cwd || process.cwd();
340
- // Cache the Claude → backend session ID mapping
341
- this.claudeToBackendSessionId.set(claudeSessionId, sessionId);
342
- logger_1.logger.info('Session started', { claudeSessionId, sessionId, cwd });
343
- // If there are other sessions in memory, mark them as INACTIVE
344
- // This happens when user does /resume - the first session should be marked INACTIVE
345
- const previousSessionIds = Array.from(this.activeSessions.keys()).filter(id => id !== sessionId);
346
- if (previousSessionIds.length > 0) {
347
- logger_1.logger.info(`Marking ${previousSessionIds.length} previous session(s) as INACTIVE`);
348
- for (const prevId of previousSessionIds) {
349
- try {
350
- await this.appSyncClient.updateSession({
351
- sessionId: prevId,
352
- status: codevibe_core_1.SessionStatus.INACTIVE,
353
- });
354
- logger_1.logger.info('Previous session marked INACTIVE', { prevId, newSessionId: sessionId });
355
- }
356
- catch (error) {
357
- logger_1.logger.warn('Failed to mark previous session as INACTIVE', { prevId, error });
358
- }
359
- // Use raw Claude session ID for port file (hooks use raw IDs)
360
- const prevState = this.activeSessions.get(prevId);
361
- if (prevState) {
362
- this.removePortFile(prevState.claudeSessionId);
363
- }
364
- this.activeSessions.delete(prevId);
365
- }
366
- }
367
- // Write port file using raw Claude session ID (hooks use raw IDs to discover port)
368
- this.writePortFile(claudeSessionId);
369
- // Create session state with authenticated user ID
370
- const userId = this.appSyncClient.getCurrentUserId();
371
- const sessionState = {
372
- sessionId,
373
- claudeSessionId,
374
- userId,
375
- projectPath: cwd,
376
- cwd,
377
- createdAt: new Date(),
378
- subscriptionActive: false,
379
- waitingForPromptResponse: false,
380
- metadata: payload.metadata || {},
381
- };
382
- this.activeSessions.set(sessionId, sessionState);
383
- // Use centralized resume/create utility from codevibe-core
384
- try {
385
- const result = await (0, codevibe_core_1.resumeOrCreateSession)({
386
- sessionId,
387
- userId: sessionState.userId,
388
- agentType: codevibe_core_1.AgentType.CLAUDE,
389
- projectPath: cwd,
390
- metadata: payload.metadata || {},
391
- }, this.appSyncClient, logger_1.logger);
392
- this.sessionKey = result.sessionKey;
393
- // Claude-specific: warn if resumed encrypted session but no key found
394
- // (device key was regenerated after session was created)
395
- if (result.resumed && !result.sessionKey) {
396
- const pluginDeviceId = await codevibe_core_1.keychainManager.getDeviceId();
397
- logger_1.logger.error('Device key not found in session encryptedKeys', { sessionId, pluginDeviceId });
398
- console.error('\n⚠️ E2E ENCRYPTION WARNING: Cannot decrypt this session!');
399
- console.error(` Your device ID (${pluginDeviceId.substring(0, 8)}...) is not in session's encryption keys.`);
400
- console.error(' This happens if your device key was regenerated after the session was created.');
401
- console.error(' SOLUTION: Start a new Claude Code session instead of resuming this one.\n');
402
- }
403
- }
404
- catch (error) {
405
- // createSession errors propagate from resumeOrCreateSession
406
- if (this.isSessionLimitExceeded(error)) {
407
- this.displaySubscriptionLimitError(error, 'session');
408
- this.activeSessions.delete(sessionId);
409
- this.removePortFile(claudeSessionId);
410
- return;
411
- }
412
- logger_1.logger.error('Failed to create/resume session:', error);
413
- }
414
- // Subscribe to mobile events for this session
415
- this.subscribeToMobileEvents(sessionId);
416
- // Start heartbeat so iOS can detect if desktop is still connected
417
- this.appSyncClient.startHeartbeat(sessionId);
418
- }
419
- /**
420
- * Handle SessionEnd hook event
421
- */
422
- async handleSessionEnd(payload) {
423
- const claudeSessionId = payload.session_id;
424
- const sessionId = this.claudeToBackendSessionId.get(claudeSessionId)
425
- || this.generateBackendSessionId(claudeSessionId);
426
- logger_1.logger.info('Session ended', {
427
- claudeSessionId,
428
- sessionId,
429
- reason: payload.metadata?.reason,
430
- });
431
- // Remove port file using raw Claude session ID (hooks use raw IDs)
432
- this.removePortFile(claudeSessionId);
433
- // Clear any waiting prompt state before ending session
434
- const sessionState = this.activeSessions.get(sessionId);
435
- if (sessionState?.waitingForPromptResponse) {
436
- logger_1.logger.info('Clearing prompt wait state - session ending', { sessionId });
437
- sessionState.waitingForPromptResponse = false;
438
- sessionState.pendingPromptId = undefined;
439
- }
440
- // Stop heartbeat
441
- this.appSyncClient.stopHeartbeat(sessionId);
442
- // Update session status in AppSync
443
- if (sessionState) {
444
- try {
445
- await this.appSyncClient.updateSession({
446
- sessionId,
447
- status: codevibe_core_1.SessionStatus.INACTIVE,
448
- });
449
- logger_1.logger.info('Session marked as INACTIVE in AppSync', { sessionId });
450
- }
451
- catch (error) {
452
- logger_1.logger.warn('Failed to update session in AppSync:', error);
453
- }
454
- }
455
- else {
456
- logger_1.logger.warn('Cannot update session - session state not found', { sessionId });
457
- }
458
- // Remove from active sessions and ID mapping
459
- this.activeSessions.delete(sessionId);
460
- this.claudeToBackendSessionId.delete(claudeSessionId);
461
- logger_1.logger.debug('Session cleanup completed', { sessionId });
462
- }
463
- /**
464
- * Register device encryption key with backend for E2E encryption
465
- * This ensures the current device's public key is known to the backend
466
- * so session keys can be encrypted for this device
467
- */
468
- async registerDeviceEncryptionKey() {
469
- try {
470
- // Get or generate device key pair (async keychain access)
471
- const deviceId = await codevibe_core_1.keychainManager.getDeviceId();
472
- const publicKey = await codevibe_core_1.keychainManager.getDevicePublicKey();
473
- const platform = codevibe_core_1.keychainManager.getDevicePlatform();
474
- const deviceName = codevibe_core_1.keychainManager.getDeviceName();
475
- logger_1.logger.info('Registering device encryption key', { deviceId, platform, deviceName });
476
- // Register with backend (will update if deviceId already exists)
477
- await this.appSyncClient.registerDeviceKey(deviceId, publicKey, platform, deviceName);
478
- codevibe_core_1.keychainManager.setIsRegistered(true);
479
- logger_1.logger.info('Device encryption key registered successfully', { deviceId });
480
- }
481
- catch (error) {
482
- // Don't fail startup if registration fails - encryption is optional
483
- logger_1.logger.warn('Failed to register device encryption key (E2E encryption may not work):', error);
484
- }
485
- }
486
- /**
487
- * Subscribe to mobile events for a session
488
- */
489
- subscribeToMobileEvents(sessionId) {
490
- logger_1.logger.info('Subscribing to mobile events', { sessionId });
491
- const sessionState = this.activeSessions.get(sessionId);
492
- if (!sessionState) {
493
- logger_1.logger.error('Session not found', { sessionId });
494
- return;
495
- }
496
- this.appSyncClient.subscribeToEvents(sessionId, async (event) => {
497
- logger_1.logger.info('Received mobile event', {
498
- eventId: event.eventId,
499
- type: event.type,
500
- sessionId: event.sessionId,
501
- isEncrypted: event.isEncrypted,
502
- });
503
- // Decrypt event content if encrypted
504
- let decryptedContent = event.content || '';
505
- if (event.isEncrypted && this.sessionKey) {
506
- try {
507
- decryptedContent = codevibe_core_1.cryptoService.decryptContent(event.content, this.sessionKey);
508
- logger_1.logger.debug('Event decrypted successfully', { eventId: event.eventId });
509
- }
510
- catch (error) {
511
- logger_1.logger.error('Failed to decrypt event:', { eventId: event.eventId, error });
512
- // Fall back to original content (might not work, but better than nothing)
513
- decryptedContent = event.content;
514
- }
515
- }
516
- // Create a modified event with decrypted content for processing
517
- const processedEvent = { ...event, content: decryptedContent };
518
- // Mark event as DELIVERED (MCP server received it) - double gray checkmark
519
- try {
520
- await this.appSyncClient.updateEventStatus({
521
- eventId: event.eventId,
522
- sessionId: event.sessionId,
523
- timestamp: event.timestamp,
524
- deliveryStatus: codevibe_core_1.DeliveryStatus.DELIVERED,
525
- });
526
- logger_1.logger.info('Event marked as DELIVERED', { eventId: event.eventId });
527
- }
528
- catch (error) {
529
- logger_1.logger.warn('Failed to mark event as DELIVERED', { eventId: event.eventId, error });
530
- }
531
- // Handle different event types from mobile
532
- if (event.type === codevibe_core_1.EventType.USER_PROMPT) {
533
- const sessionState = this.activeSessions.get(sessionId);
534
- // Check if session is waiting for prompt response
535
- if (sessionState?.waitingForPromptResponse) {
536
- // Parse the input to determine action
537
- const content = decryptedContent.trim();
538
- const optionCount = sessionState.pendingSubmitMap
539
- ? Object.keys(sessionState.pendingSubmitMap).length
540
- : 3;
541
- const parsed = this.parseInteractivePromptInput(content, optionCount);
542
- logger_1.logger.info('Parsed interactive prompt input', {
543
- sessionId,
544
- content,
545
- parsed,
546
- hasSubmitMap: !!sessionState.pendingSubmitMap,
547
- });
548
- if (parsed.action === 'select_option') {
549
- // Translate via submitMap before sending to terminal
550
- const terminalInput = sessionState.pendingSubmitMap?.[parsed.option] || parsed.option;
551
- logger_1.logger.info('User selected option', { option: parsed.option, terminalInput });
552
- const success = await this.promptResponder.answerInteractivePrompt(sessionId, terminalInput);
553
- if (success) {
554
- await this.markEventExecuted(event);
555
- sessionState.waitingForPromptResponse = false;
556
- sessionState.pendingPromptId = undefined;
557
- sessionState.pendingSubmitMap = undefined;
558
- await this.appSyncClient.createEvent({
559
- sessionId,
560
- type: codevibe_core_1.EventType.NOTIFICATION,
561
- source: codevibe_core_1.EventSource.DESKTOP,
562
- content: `Selected option ${parsed.option}`,
563
- metadata: { promptAnswered: true },
564
- });
565
- }
566
- else {
567
- await this.sendPromptError(sessionId, 'Failed to select option');
568
- }
569
- }
570
- else if (parsed.action === 'option_with_followup') {
571
- // Translate option via submitMap, send it, then send follow-up text
572
- const terminalInput = sessionState.pendingSubmitMap?.[parsed.option] || parsed.option;
573
- logger_1.logger.info('User selected option with follow-up', {
574
- option: parsed.option,
575
- terminalInput,
576
- followUpText: parsed.followUpText,
577
- });
578
- const optionSuccess = await this.promptResponder.answerInteractivePrompt(sessionId, terminalInput);
579
- // Clear waiting state
580
- sessionState.waitingForPromptResponse = false;
581
- sessionState.pendingPromptId = undefined;
582
- sessionState.pendingSubmitMap = undefined;
583
- if (optionSuccess) {
584
- await this.appSyncClient.createEvent({
585
- sessionId,
586
- type: codevibe_core_1.EventType.NOTIFICATION,
587
- source: codevibe_core_1.EventSource.DESKTOP,
588
- content: `Selected option ${parsed.option}`,
589
- metadata: { promptAnswered: true },
590
- });
591
- // If there's follow-up text, send it after a short delay
592
- if (parsed.followUpText) {
593
- // Wait for Claude to process the option selection
594
- await new Promise(resolve => setTimeout(resolve, 1000));
595
- // Send the follow-up text as a new prompt
596
- const modifiedEvent = { ...event, content: parsed.followUpText };
597
- await this.executeMobilePrompt(sessionId, modifiedEvent);
598
- }
599
- await this.markEventExecuted(event);
600
- }
601
- else {
602
- await this.sendPromptError(sessionId, 'Failed to select option');
603
- }
604
- }
605
- else {
606
- // 'send_as_response' - send content as-is to interactive prompt
607
- logger_1.logger.info('Sending as free-form response to interactive prompt', {
608
- response: content,
609
- });
610
- const success = await this.promptResponder.answerInteractivePrompt(sessionId, content);
611
- if (success) {
612
- await this.markEventExecuted(event);
613
- sessionState.waitingForPromptResponse = false;
614
- sessionState.pendingPromptId = undefined;
615
- sessionState.pendingSubmitMap = undefined;
616
- await this.appSyncClient.createEvent({
617
- sessionId,
618
- type: codevibe_core_1.EventType.NOTIFICATION,
619
- source: codevibe_core_1.EventSource.DESKTOP,
620
- content: `Response sent to interactive prompt`,
621
- metadata: { promptAnswered: true },
622
- });
623
- }
624
- else {
625
- await this.sendPromptError(sessionId, 'Failed to send response');
626
- }
627
- }
628
- }
629
- else {
630
- // No interactive prompt waiting - execute as regular prompt
631
- await this.executeMobilePrompt(sessionId, processedEvent);
632
- }
633
- }
634
- }, (error) => {
635
- logger_1.logger.error('Subscription error', { sessionId, error });
636
- // TODO: Implement reconnection logic
637
- });
638
- sessionState.subscriptionActive = true;
639
- logger_1.logger.info('Subscription active', { sessionId });
640
- }
641
- /**
642
- * Send INTERACTIVE_PROMPT to AppSync after capturing dynamic options from tmux.
643
- * Waits 500ms for Claude Code to render the prompt, then captures the terminal.
644
- */
645
- async sendInteractivePromptAsync(backendSessionId, payload, content) {
646
- // Wait for hook to return and Claude Code to render the prompt
647
- await new Promise(resolve => setTimeout(resolve, 500));
648
- const tmuxSession = process.env.CODEVIBE_TMUX_SESSION;
649
- const metadata = { ...(payload.metadata || {}) };
650
- if (tmuxSession) {
651
- try {
652
- const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
653
- const execAsync = (cmd) => new Promise((resolve, reject) => {
654
- exec(cmd, { timeout: 5000 }, (err, stdout) => {
655
- if (err)
656
- reject(err);
657
- else
658
- resolve({ stdout: stdout || '' });
659
- });
660
- });
661
- const { stdout } = await execAsync(`tmux capture-pane -p -e -S -30 -t '${tmuxSession}'`);
662
- // Log captured snapshot for debugging
663
- const snapshotLines = stdout.split('\n');
664
- logger_1.logger.info('tmux capture result', {
665
- tmuxSession,
666
- totalLines: snapshotLines.length,
667
- lastLines: snapshotLines.slice(-15).map(l => l.replace(/\x1B[^m]*m/g, '').trim()).filter(Boolean),
668
- });
669
- const parsed = (0, codevibe_core_1.parseInteractivePrompt)(stdout);
670
- if (parsed && parsed.options.length > 0) {
671
- metadata.options = parsed.options;
672
- metadata.submitMap = parsed.submitMap;
673
- metadata.instructions = this.buildPromptInstructions(parsed);
674
- logger_1.logger.info('Parsed dynamic options from tmux', {
675
- optionCount: parsed.options.length,
676
- kind: parsed.kind,
677
- options: parsed.options,
678
- });
679
- }
680
- else {
681
- logger_1.logger.info('No dynamic options parsed from tmux, using fallback', {
682
- parsedResult: parsed,
683
- });
684
- this.addFallbackOptions(metadata);
685
- }
686
- }
687
- catch (e) {
688
- logger_1.logger.warn('Failed to capture tmux pane for options', { error: e });
689
- this.addFallbackOptions(metadata);
690
- }
691
- }
692
- else {
693
- logger_1.logger.warn('No tmux session — using fallback options');
694
- this.addFallbackOptions(metadata);
695
- }
696
- // Store submitMap in session state for response translation
697
- const sessionState = this.activeSessions.get(backendSessionId);
698
- if (sessionState && metadata.submitMap) {
699
- sessionState.pendingSubmitMap = metadata.submitMap;
700
- }
701
- // Encrypt and send to AppSync
702
- let eventContent = content;
703
- let eventMetadata = metadata;
704
- let isEncrypted = false;
705
- if (this.sessionKey) {
706
- eventContent = codevibe_core_1.cryptoService.encryptContent(content, this.sessionKey);
707
- const encryptedMeta = codevibe_core_1.cryptoService.encryptMetadata(eventMetadata, this.sessionKey);
708
- eventMetadata = { encrypted: encryptedMeta };
709
- isEncrypted = true;
710
- }
711
- await this.appSyncClient.createEvent({
712
- sessionId: backendSessionId,
713
- type: codevibe_core_1.EventType.INTERACTIVE_PROMPT,
714
- source: payload.source,
715
- content: eventContent,
716
- metadata: eventMetadata,
717
- promptId: payload.prompt_id,
718
- isEncrypted: isEncrypted ? true : undefined,
719
- });
720
- logger_1.logger.info('Interactive prompt sent to AppSync with dynamic options', {
721
- sessionId: backendSessionId,
722
- });
723
- }
724
- addFallbackOptions(metadata) {
725
- metadata.options = [
726
- { number: '1', text: 'Yes' },
727
- { number: '2', text: 'Yes, and don\'t ask again' },
728
- { number: '3', text: 'Reject and tell Claude what to do differently' },
729
- ];
730
- metadata.submitMap = { '1': '1', '2': '2', '3': '3' };
731
- metadata.instructions = 'Reply with 1, 2, or 3. Append a message to provide alternative instructions.';
732
- }
733
- buildPromptInstructions(parsed) {
734
- const nums = parsed.options.map(o => o.number).join(', ');
735
- return `Reply with ${nums}. Append a message to provide alternative instructions.`;
736
- }
737
- /**
738
- * Parse mobile input when an interactive prompt is waiting.
739
- * Dynamically supports any number of options (not hardcoded to 3).
740
- *
741
- * Input patterns:
742
- * - "N" (exact number) → select that option
743
- * - "N <text>" → select option N, then send <text> as follow-up prompt
744
- * - anything else → send as free-form response
745
- */
746
- parseInteractivePromptInput(content, optionCount = 3) {
747
- return parseInteractivePromptInput(content, optionCount);
748
- }
749
- /**
750
- * Helper to mark an event as EXECUTED
751
- */
752
- async markEventExecuted(event) {
753
- try {
754
- await this.appSyncClient.updateEventStatus({
755
- eventId: event.eventId,
756
- sessionId: event.sessionId,
757
- timestamp: event.timestamp,
758
- deliveryStatus: codevibe_core_1.DeliveryStatus.EXECUTED,
759
- });
760
- logger_1.logger.info('Event marked as EXECUTED', { eventId: event.eventId });
761
- }
762
- catch (error) {
763
- logger_1.logger.warn('Failed to mark event as EXECUTED', { eventId: event.eventId, error });
764
- }
765
- }
766
- /**
767
- * Helper to send error notification to mobile
768
- */
769
- async sendPromptError(sessionId, message) {
770
- await this.appSyncClient.createEvent({
771
- sessionId,
772
- type: codevibe_core_1.EventType.NOTIFICATION,
773
- source: codevibe_core_1.EventSource.DESKTOP,
774
- content: message,
775
- metadata: { error: true },
776
- });
777
- }
778
- /**
779
- * Check if error is a subscription limit exceeded error
780
- */
781
- isSessionLimitExceeded(error) {
782
- const errorMessage = this.getErrorMessage(error);
783
- return errorMessage.includes('SESSION_LIMIT_EXCEEDED');
784
- }
785
- /**
786
- * Check if error is a usage limit exceeded error (message or image)
787
- */
788
- isUsageLimitExceeded(error) {
789
- const errorMessage = this.getErrorMessage(error);
790
- return errorMessage.includes('MESSAGE_LIMIT_EXCEEDED') || errorMessage.includes('IMAGE_LIMIT_EXCEEDED');
791
- }
792
- /**
793
- * Extract error message from various error types
794
- */
795
- getErrorMessage(error) {
796
- if (error instanceof Error) {
797
- return error.message;
798
- }
799
- if (typeof error === 'object' && error !== null) {
800
- const errorObj = error;
801
- // Check for GraphQL error format
802
- if (errorObj.errors && Array.isArray(errorObj.errors)) {
803
- return errorObj.errors.map((e) => e.message || '').join(' ');
804
- }
805
- // Check for message property
806
- if (typeof errorObj.message === 'string') {
807
- return errorObj.message;
808
- }
809
- }
810
- return String(error);
811
- }
812
- /**
813
- * Display subscription limit error to user
814
- */
815
- displaySubscriptionLimitError(error, limitType) {
816
- const errorMessage = this.getErrorMessage(error);
817
- // Extract tier and limit info from error message if available
818
- let tierInfo = '';
819
- const tierMatch = errorMessage.match(/for your (\w+) plan/i);
820
- if (tierMatch) {
821
- tierInfo = ` (${tierMatch[1]} tier)`;
822
- }
823
- let limitInfo = '';
824
- const limitMatch = errorMessage.match(/of (\d+)/);
825
- if (limitMatch) {
826
- limitInfo = ` [Limit: ${limitMatch[1]}]`;
827
- }
828
- // Display user-friendly console message with formatting
829
- console.log('\n' + '='.repeat(60));
830
- console.log('⚠️ SUBSCRIPTION LIMIT REACHED');
831
- console.log('='.repeat(60));
832
- switch (limitType) {
833
- case 'session':
834
- console.log(`You have reached the maximum number of active sessions${tierInfo}.`);
835
- console.log(`${limitInfo}`);
836
- console.log('\nTo continue, please:');
837
- console.log(' • Close an existing Claude Code session, or');
838
- console.log(' • Upgrade your subscription in the CodeVibe iOS app');
839
- break;
840
- case 'message':
841
- console.log(`You have reached your monthly message limit${tierInfo}.`);
842
- console.log(`${limitInfo}`);
843
- console.log('\nTo continue, please:');
844
- console.log(' • Wait until your usage resets next month, or');
845
- console.log(' • Upgrade your subscription in the CodeVibe iOS app');
846
- break;
847
- case 'image':
848
- console.log(`You have reached your monthly image attachment limit${tierInfo}.`);
849
- console.log(`${limitInfo}`);
850
- console.log('\nTo continue, please:');
851
- console.log(' • Wait until your usage resets next month, or');
852
- console.log(' • Upgrade your subscription in the CodeVibe iOS app');
853
- break;
854
- }
855
- console.log('\nNote: You can still use Claude Code normally from your desktop.');
856
- console.log('This limit only affects syncing with the mobile app.');
857
- console.log('='.repeat(60) + '\n');
858
- logger_1.logger.error('Subscription limit exceeded', { limitType, errorMessage });
859
- }
860
- /**
861
- * Download an attachment from S3 and save it to a temp file
862
- * If the attachment is encrypted, decrypts it using the session key
863
- * Returns the local file path
864
- * @param attachment The attachment to download
865
- * @param sessionId The session ID for organizing temp files
866
- * @param eventIsEncrypted Whether the parent event is encrypted (fallback for attachment.isEncrypted)
867
- */
868
- async downloadAttachment(attachment, sessionId, eventIsEncrypted) {
869
- try {
870
- // Use attachment.isEncrypted if available, otherwise fall back to event.isEncrypted
871
- // This handles AppSync subscription limitation where nested object fields may be null
872
- const shouldDecrypt = attachment.isEncrypted ?? eventIsEncrypted ?? false;
873
- logger_1.logger.info('Downloading attachment - START', {
874
- id: attachment.id,
875
- type: attachment.type,
876
- filename: attachment.filename,
877
- s3Key: attachment.s3Key,
878
- attachmentIsEncrypted: attachment.isEncrypted,
879
- eventIsEncrypted,
880
- shouldDecrypt,
881
- hasSessionKey: !!this.sessionKey,
882
- });
883
- // Get pre-signed download URL
884
- const { downloadUrl } = await this.appSyncClient.getAttachmentDownloadUrl(attachment.s3Key);
885
- // Download the file
886
- const response = await fetch(downloadUrl);
887
- if (!response.ok) {
888
- throw new Error(`Failed to download attachment: ${response.status} ${response.statusText}`);
889
- }
890
- let buffer = Buffer.from(await response.arrayBuffer());
891
- logger_1.logger.info('Attachment downloaded', {
892
- id: attachment.id,
893
- downloadedSize: buffer.length,
894
- first20Bytes: buffer.slice(0, 20).toString('hex'),
895
- });
896
- // Decrypt if encrypted
897
- logger_1.logger.info('Checking decryption conditions', {
898
- id: attachment.id,
899
- shouldDecrypt,
900
- hasSessionKey: !!this.sessionKey,
901
- willDecrypt: !!(shouldDecrypt && this.sessionKey),
902
- });
903
- if (shouldDecrypt && this.sessionKey) {
904
- try {
905
- logger_1.logger.info('Decrypting attachment', { id: attachment.id, encryptedSize: buffer.length });
906
- buffer = codevibe_core_1.cryptoService.decryptData(buffer, this.sessionKey);
907
- logger_1.logger.info('Attachment decrypted successfully', {
908
- id: attachment.id,
909
- decryptedSize: buffer.length,
910
- first20Bytes: buffer.slice(0, 20).toString('hex'),
911
- });
912
- }
913
- catch (decryptError) {
914
- logger_1.logger.error('Failed to decrypt attachment:', { id: attachment.id, error: decryptError });
915
- throw new Error('Failed to decrypt attachment');
916
- }
917
- }
918
- else if (shouldDecrypt && !this.sessionKey) {
919
- logger_1.logger.warn('Cannot decrypt attachment - no session key available', { id: attachment.id });
920
- // Continue with encrypted data - Claude Code won't be able to view it properly
921
- }
922
- else {
923
- logger_1.logger.info('Skipping decryption - attachment not encrypted or no session key', {
924
- id: attachment.id,
925
- shouldDecrypt,
926
- hasSessionKey: !!this.sessionKey,
927
- });
928
- }
929
- // Create temp directory for this session if it doesn't exist
930
- const tempDir = path.join(os.tmpdir(), 'codevibe-claude', sessionId);
931
- if (!fs.existsSync(tempDir)) {
932
- fs.mkdirSync(tempDir, { recursive: true });
933
- }
934
- // Determine file extension from MIME type or filename
935
- let extension = '';
936
- // If filename is encrypted, decrypt it
937
- let actualFilename = attachment.filename;
938
- if (shouldDecrypt && attachment.filename && this.sessionKey) {
939
- try {
940
- actualFilename = codevibe_core_1.cryptoService.decryptContent(attachment.filename, this.sessionKey);
941
- }
942
- catch {
943
- // Filename decryption failed, use as-is (might be plaintext)
944
- actualFilename = attachment.filename;
945
- }
946
- }
947
- if (actualFilename) {
948
- const ext = path.extname(actualFilename);
949
- if (ext)
950
- extension = ext;
951
- }
952
- if (!extension) {
953
- // Fallback to MIME type
954
- const mimeToExt = {
955
- 'image/jpeg': '.jpg',
956
- 'image/png': '.png',
957
- 'image/gif': '.gif',
958
- 'image/webp': '.webp',
959
- 'image/heic': '.heic',
960
- 'application/pdf': '.pdf',
961
- };
962
- extension = mimeToExt[attachment.type] || '.bin';
963
- }
964
- // Save to temp file
965
- const filename = `attachment-${attachment.id}${extension}`;
966
- const filePath = path.join(tempDir, filename);
967
- fs.writeFileSync(filePath, buffer);
968
- logger_1.logger.info('Attachment saved to temp file', {
969
- id: attachment.id,
970
- filePath,
971
- size: buffer.length,
972
- wasDecrypted: shouldDecrypt && !!this.sessionKey,
973
- });
974
- return filePath;
975
- }
976
- catch (error) {
977
- logger_1.logger.error('Failed to download attachment:', { id: attachment.id, error });
978
- return null;
979
- }
980
- }
981
- /**
982
- * Execute a prompt from mobile in the desktop Claude Code session
983
- * Uses tmux send-keys to send the prompt to the Claude Code session
984
- * If the event has attachments, downloads them and includes file paths in prompt
985
- */
986
- async executeMobilePrompt(sessionId, event) {
987
- let prompt = event.content || '';
988
- const attachments = event.attachments || [];
989
- logger_1.logger.info('Executing mobile prompt via tmux', {
990
- sessionId,
991
- promptLength: prompt.length,
992
- attachmentCount: attachments.length,
993
- });
994
- // Download attachments and build file paths to include in prompt
995
- const attachmentPaths = [];
996
- if (attachments.length > 0) {
997
- logger_1.logger.info('Downloading attachments for prompt', { count: attachments.length });
998
- for (const attachment of attachments) {
999
- const filePath = await this.downloadAttachment(attachment, sessionId, event.isEncrypted // Pass event encryption status as fallback
1000
- );
1001
- if (filePath) {
1002
- attachmentPaths.push(filePath);
1003
- }
1004
- }
1005
- // If we have downloaded files, prepend them to the prompt
1006
- if (attachmentPaths.length > 0) {
1007
- const fileReferences = attachmentPaths
1008
- .map(p => `[Attached file: ${p}]`)
1009
- .join('\n');
1010
- // Format: file references first, then the user's message
1011
- if (prompt) {
1012
- prompt = `${fileReferences}\n\n${prompt}`;
1013
- }
1014
- else {
1015
- // No text content, just file references with instruction
1016
- prompt = `${fileReferences}\n\nPlease analyze the attached file(s).`;
1017
- }
1018
- logger_1.logger.info('Prompt updated with attachment paths', {
1019
- attachmentCount: attachmentPaths.length,
1020
- newPromptLength: prompt.length,
1021
- });
1022
- }
1023
- }
1024
- // Track this prompt to filter out the duplicate USER_PROMPT from the hook
1025
- this.trackMobilePrompt(sessionId, prompt);
1026
- try {
1027
- // Use tmux send-keys to send the prompt (same as answering interactive prompts)
1028
- const success = await this.promptResponder.answerInteractivePrompt(sessionId, prompt);
1029
- if (success) {
1030
- // Mark event as EXECUTED (fed into Claude Code) - double blue checkmark
1031
- try {
1032
- await this.appSyncClient.updateEventStatus({
1033
- eventId: event.eventId,
1034
- sessionId: event.sessionId,
1035
- timestamp: event.timestamp,
1036
- deliveryStatus: codevibe_core_1.DeliveryStatus.EXECUTED,
1037
- });
1038
- logger_1.logger.info('Event marked as EXECUTED', { eventId: event.eventId });
1039
- }
1040
- catch (error) {
1041
- logger_1.logger.warn('Failed to mark event as EXECUTED', { eventId: event.eventId, error });
1042
- }
1043
- logger_1.logger.info('Mobile prompt sent successfully', { sessionId });
1044
- // Send confirmation to mobile
1045
- const confirmationMsg = attachmentPaths.length > 0
1046
- ? `Prompt with ${attachmentPaths.length} attachment(s) sent to Claude Code`
1047
- : `Prompt "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}" sent to Claude Code`;
1048
- await this.appSyncClient.createEvent({
1049
- sessionId,
1050
- type: codevibe_core_1.EventType.NOTIFICATION,
1051
- source: codevibe_core_1.EventSource.DESKTOP,
1052
- content: confirmationMsg,
1053
- metadata: {
1054
- mobilePrompt: true,
1055
- attachmentCount: attachmentPaths.length,
1056
- },
1057
- });
1058
- }
1059
- else {
1060
- logger_1.logger.error('Failed to send mobile prompt', { sessionId });
1061
- // Send error notification to mobile
1062
- await this.appSyncClient.createEvent({
1063
- sessionId,
1064
- type: codevibe_core_1.EventType.NOTIFICATION,
1065
- source: codevibe_core_1.EventSource.DESKTOP,
1066
- content: `Failed to send prompt to Claude Code`,
1067
- metadata: {
1068
- error: true,
1069
- },
1070
- });
1071
- }
1072
- }
1073
- catch (error) {
1074
- logger_1.logger.error('Failed to execute mobile prompt:', error);
1075
- }
1076
- }
1077
- }
1078
- McpServer.MOBILE_PROMPT_EXPIRY_MS = 3000; // 3 seconds
1079
- // Main entry point
1080
- async function main() {
1081
- // Get session ID from command line argument or environment variable
1082
- const sessionId = process.argv[2] || process.env.CLAUDE_SESSION_ID;
1083
- if (sessionId) {
1084
- logger_1.logger.info(`Starting MCP server for session: ${sessionId}`);
1085
- }
1086
- else {
1087
- logger_1.logger.info('Starting MCP server without initial session ID (will be set on SessionStart)');
1088
- }
1089
- const server = new McpServer(sessionId);
1090
- try {
1091
- await server.start();
1092
- // Output the assigned port for the session-start hook to capture
1093
- const port = server.getPort();
1094
- console.log(`PORT=${port}`);
1095
- // Handle graceful shutdown
1096
- let isShuttingDown = false;
1097
- const shutdown = async (signal) => {
1098
- if (isShuttingDown) {
1099
- logger_1.logger.info('Shutdown already in progress, ignoring additional signal');
1100
- return;
1101
- }
1102
- isShuttingDown = true;
1103
- logger_1.logger.info(`Received ${signal} signal, stopping server...`);
1104
- try {
1105
- await server.stop();
1106
- logger_1.logger.info('Graceful shutdown completed');
1107
- process.exit(0);
1108
- }
1109
- catch (error) {
1110
- logger_1.logger.error('Error during shutdown:', error);
1111
- process.exit(1);
1112
- }
1113
- };
1114
- process.on('SIGINT', () => shutdown('SIGINT'));
1115
- process.on('SIGTERM', () => shutdown('SIGTERM'));
1116
- process.on('SIGHUP', () => shutdown('SIGHUP'));
1117
- // Handle uncaught exceptions - try to cleanup before exit
1118
- process.on('uncaughtException', async (error) => {
1119
- logger_1.logger.error('Uncaught exception:', error);
1120
- await shutdown('uncaughtException');
1121
- });
1122
- process.on('unhandledRejection', async (reason) => {
1123
- logger_1.logger.error('Unhandled rejection:', reason);
1124
- await shutdown('unhandledRejection');
1125
- });
1126
- }
1127
- catch (error) {
1128
- logger_1.logger.error('Failed to start MCP Server:', error);
1129
- process.exit(1);
1130
- }
1131
- }
1132
- /**
1133
- * Parse interactive prompt input — exported for testing
1134
- */
1135
- function parseInteractivePromptInput(content, optionCount = 3) {
1136
- const trimmed = content.trim();
1137
- // Check for exact option number (e.g., "1", "2", "3")
1138
- const exactMatch = trimmed.match(/^(\d+)$/);
1139
- if (exactMatch) {
1140
- const num = parseInt(exactMatch[1]);
1141
- if (num >= 1 && num <= optionCount) {
1142
- return { action: 'select_option', option: exactMatch[1] };
1143
- }
1144
- }
1145
- // Check for "N <text>", "N,<text>", "N.<text>", "N\n<text>" pattern (option + follow-up)
1146
- // Supports separators: whitespace, comma, period, semicolon, colon, dash, or combinations
1147
- const withTextMatch = trimmed.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);
1148
- if (withTextMatch) {
1149
- const num = parseInt(withTextMatch[1]);
1150
- if (num >= 1 && num <= optionCount) {
1151
- return { action: 'option_with_followup', option: withTextMatch[1], followUpText: withTextMatch[2].trim() };
1152
- }
1153
- }
1154
- // Anything else — send as free-form response
1155
- return { action: 'send_as_response' };
1156
- }
1157
- // Start the server
1158
- main().catch((error) => {
1159
- logger_1.logger.error('Unhandled error in main:', error);
1160
- process.exit(1);
1161
- });
1162
- //# sourceMappingURL=server.js.map
1
+ "use strict";var G=Object.create;var P=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var Q=(m,e)=>{for(var s in e)P(m,s,{get:e[s],enumerable:!0})},F=(m,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of z(e))!J.call(m,i)&&i!==s&&P(m,i,{get:()=>e[i],enumerable:!(t=Y(e,i))||t.enumerable});return m};var y=(m,e,s)=>(s=m!=null?G(W(m)):{},F(e||!m||!m.__esModule?P(s,"default",{value:m,enumerable:!0}):s,m)),Z=m=>F(P({},"__esModule",{value:!0}),m);var te={};Q(te,{parseInteractivePromptInput:()=>B});module.exports=Z(te);var v=y(require("fs")),E=y(require("path")),T=y(require("os"));var O=y(require("os")),D=y(require("path")),N=require("@quantiya/codevibe-core"),n=(0,N.createLogger)({name:"codevibe-claude",logFile:D.default.join(O.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var a=require("@quantiya/codevibe-core");var R=y(require("express")),w=y(require("fs")),M=y(require("path")),k=y(require("os")),$=require("@quantiya/codevibe-core");var l=require("@quantiya/codevibe-core");var I=class{constructor(){this.assignedPort=0;this.app=(0,R.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(R.default.json({limit:"1mb"})),this.app.use((e,s,t)=>{n.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),t()}),this.app.use((e,s,t,i)=>{n.error("Express error:",e);let r={success:!1,error:e.message||"Internal server error"};t.status(500).json(r)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,s){let t={success:!0,data:{status:"healthy",uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}};s.json(t)}async handleEvent(e,s){try{let t=e.body;if(!t.session_id){let o={success:!1,error:"Missing required field: session_id"};s.status(400).json(o);return}if(!t.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};s.status(400).json(o);return}let i=this.transformHookToEvent(t);n.info("Received event from hook",{sessionId:t.session_id,hookEvent:t.hook_event_name,type:i.type}),this.eventHandler?await this.eventHandler(i):n.warn("No event handler registered");let r={success:!0,message:"Event processed successfully"};s.json(r)}catch(t){n.error("Error handling event:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}async handleTestExecute(e,s){try{let{sessionId:t,prompt:i}=e.body;if(!t||!i){let o={success:!1,error:"Missing required fields: sessionId, prompt"};s.status(400).json(o);return}n.info("Test execute request",{sessionId:t,prompt:i});let r={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:t,prompt:i}};s.json(r)}catch(t){n.error("Error in test execute:",t);let i={success:!1,error:t instanceof Error?t.message:"Unknown error"};s.status(500).json(i)}}transformHookToEvent(e){let s,t,i={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)s=e.type,t=e.content;else switch(e.hook_event_name){case"SessionStart":s=l.EventType.NOTIFICATION,t="Session started",i.source=e.source;break;case"SessionEnd":s=l.EventType.NOTIFICATION,t=`Session ended: ${e.reason||"unknown"}`,i.reason=e.reason;break;case"UserPromptSubmit":s=l.EventType.USER_PROMPT,t=e.prompt||"";break;case"PostToolUse":s=l.EventType.TOOL_USE,t=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),i.tool_name=e.tool_name;break;case"Notification":s=l.EventType.NOTIFICATION,t=e.message||"",i.notification_type=e.notification_type;break;default:s=l.EventType.NOTIFICATION,t=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:s,source:l.EventSource.DESKTOP,content:t,metadata:i}}onEvent(e){this.eventHandler=e}async start(e){let s=e||this.sessionId;return s&&(this.sessionId=s),new Promise((t,i)=>{try{let r=(0,$.getConfig)(),o=r.server.dynamicPort?0:r.server.port;this.server=this.app.listen(o,r.server.host,()=>{let p=this.server.address();this.assignedPort=p.port,n.info(`HTTP API listening on http://${r.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),t(this.assignedPort)}),this.server.on("error",p=>{n.error("HTTP server error:",p),i(p)})}catch(r){i(r)}})}writePortFile(e,s){let t=M.join(k.tmpdir(),`codevibe-claude-${e}.port`);try{w.writeFileSync(t,s.toString()),n.info(`Port file written: ${t} -> ${s}`)}catch(i){n.error(`Failed to write port file: ${t}`,i)}}removePortFile(){if(this.sessionId){let e=M.join(k.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{w.existsSync(e)&&(w.unlinkSync(e),n.info(`Port file removed: ${e}`))}catch(s){n.warn(`Failed to remove port file: ${e}`,s)}}}async stop(){return new Promise((e,s)=>{this.removePortFile(),this.server?this.server.close(t=>{t?(n.error("Error stopping HTTP server:",t),s(t)):(n.info("HTTP API stopped"),e())}):e()})}};var K=require("child_process"),U=require("@quantiya/codevibe-core");var b=class{async executePrompt(e,s){let t=(0,U.getConfig)(),i=t.claude.defaultTimeout;return n.info("Executing prompt from mobile",{sessionId:e,promptLength:s.length,timeout:i}),new Promise(r=>{let o=["--resume",e,"--print","--output-format","stream-json",s];n.debug("Spawning Claude command",{command:t.claude.command,args:o});let p=(0,K.spawn)(t.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),c="",d="",u=!1,h=setTimeout(()=>{u=!0,n.warn("Command execution timed out",{sessionId:e,timeout:i}),p.kill("SIGTERM")},i);p.stdout?.on("data",g=>{let f=g.toString();c+=f,n.debug("Command stdout",{output:f.slice(0,200)})}),p.stderr?.on("data",g=>{let f=g.toString();d+=f,n.debug("Command stderr",{output:f.slice(0,200)})}),p.on("close",g=>{clearTimeout(h);let f={success:g===0&&!u,output:c,error:d,exitCode:g||void 0,timedOut:u};f.success?n.info("Command executed successfully",{sessionId:e,exitCode:g,outputLength:c.length}):n.error("Command execution failed",{sessionId:e,exitCode:g,timedOut:u,error:d.slice(0,500)}),r(f)}),p.on("error",g=>{clearTimeout(h),n.error("Failed to spawn command",{error:g.message}),r({success:!1,error:g.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(t=>t.test(e))}extractPromptText(e){let s=e.split(`
2
+ `);for(let t=s.length-1;t>=0;t--){let i=s[t].trim();if(this.detectInteractivePrompt(i))return i}return null}};var L=require("child_process"),H=require("util");var j=(0,H.promisify)(L.exec),C=class{async answerInteractivePrompt(e,s){n.info("Attempting to answer interactive prompt",{sessionId:e,response:s});try{let t=process.env.CODEVIBE_TMUX_SESSION;return n.info("Checking tmux session environment",{tmuxSession:t||"(not set)",allEnvKeys:Object.keys(process.env).filter(i=>i.includes("CODEVIBE")||i.includes("TMUX"))}),t?(n.info("Using tmux send-keys",{tmuxSession:t}),await this.sendViaTmux(t,s),n.info("Successfully sent response to interactive prompt",{sessionId:e,response:s}),!0):(n.error("No tmux session found - codevibe-claude wrapper is required",{sessionId:e,hint:"Start Claude Code using the codevibe-claude wrapper script"}),!1)}catch(t){return n.error("Failed to answer interactive prompt",{sessionId:e,error:t instanceof Error?t.message:String(t)}),!1}}async sendViaTmux(e,s){let t=s.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");n.info("Sending via tmux",{sessionName:e,inputLength:s.length});try{let i=`tmux send-keys -t "${e}" -l "${t}"`,r=await j(i);n.info("tmux send-keys (text) completed",{stdout:r.stdout||"(empty)",stderr:r.stderr||"(empty)"}),await this.delay(500);let o=`tmux send-keys -t "${e}" Enter`,p=await j(o);n.info("tmux send-keys (Enter) completed",{stdout:p.stdout||"(empty)",stderr:p.stderr||"(empty)"})}catch(i){throw n.error("tmux send-keys failed",{sessionName:e,error:i}),i}}delay(e){return new Promise(s=>setTimeout(s,e))}isPromptResponse(e){let s=e.trim().toLowerCase();return!!(s==="y"||s==="n"||s==="yes"||s==="no"||/^[0-9]+$/.test(s)||/^[a-z]$/.test(s)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(s))}};var A=class m{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.sessionKey=null;this.claudeToBackendSessionId=new Map;this.pendingMobilePrompts=new Map;this.httpApi=new I,this.commandExecutor=new b,this.promptResponder=new C,this.initialSessionId=e}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,s){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:s.trim(),timestamp:Date.now()}),n.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:s.length})}isRecentMobilePrompt(e,s){let t=this.pendingMobilePrompts.get(e);if(!t)return!1;let i=Date.now(),r=s.trim(),o=[],p=!1;for(let c of t)if(!(i-c.timestamp>m.MOBILE_PROMPT_EXPIRY_MS)){if(!p&&c.prompt===r){p=!0,n.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(c)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),p}writePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.writeFileSync(s,this.assignedPort.toString()),n.info(`Port file written: ${s} -> ${this.assignedPort}`)}catch(t){n.error(`Failed to write port file: ${s}`,t)}}removePortFile(e){let s=E.join(T.tmpdir(),`codevibe-claude-${e}.port`);try{v.existsSync(s)&&(v.unlinkSync(s),n.info(`Port file removed: ${s}`))}catch(t){n.warn(`Failed to remove port file: ${s}`,t)}}async start(){try{n.info("Starting CodeVibe MCP Server...",{environment:(0,a.getEnvironment)()}),this.appSyncClient=new a.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()?(n.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await this.registerDeviceEncryptionKey()):(n.error('Authentication failed. Run "codevibe-claude login" first.'),console.error('Not authenticated. Run "codevibe-claude login" to sign in.'),process.exit(1)),this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),n.info("MCP Server started successfully",{port:this.assignedPort,host:(0,a.getConfig)().server.host,dynamicPort:(0,a.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw n.error("Failed to start MCP Server:",e),e}}async stop(){n.info("Stopping MCP Server...");let e=Array.from(this.activeSessions.keys());n.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let s of e)try{await this.appSyncClient.updateSession({sessionId:s,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE during shutdown",{sessionId:s});let t=this.activeSessions.get(s);t&&this.removePortFile(t.claudeSessionId)}catch(t){n.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:s,error:t})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop(),n.info("MCP Server stopped")}async handleEventFromHook(e){let{session_id:s,hook_event_name:t,type:i,content:r}=e;n.info("Processing hook event",{sessionId:s,hookEvent:t,type:i});try{t==="SessionStart"?await this.handleSessionStart(e):t==="SessionEnd"&&await this.handleSessionEnd(e);let o=this.claudeToBackendSessionId.get(s)||this.generateBackendSessionId(s);if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP&&t==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(o,r)){n.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:o,contentLength:r.length});return}if(i===a.EventType.INTERACTIVE_PROMPT){let h=this.activeSessions.get(o);h&&(h.waitingForPromptResponse=!0,h.pendingPromptId=e.prompt_id,n.info("Interactive prompt detected - will parse options from tmux",{sessionId:o,promptId:e.prompt_id})),this.sendInteractivePromptAsync(o,e,r).catch(g=>{n.error("Failed to send interactive prompt with dynamic options",{error:g})});return}let p=r,c=e.metadata,d=!1;n.info("Hook event encryption state",{type:i,sessionId:o,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0}),this.sessionKey?(p=a.cryptoService.encryptContent(r,this.sessionKey),c&&(c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)}),d=!0,n.info("Event encrypted for hook",{type:i,sessionId:o,isEncrypted:!0})):n.warn("No session key - event will NOT be encrypted",{type:i,sessionId:o});let u=await this.appSyncClient.createEvent({sessionId:o,type:i,source:e.source,content:p,metadata:c,promptId:e.prompt_id,isEncrypted:d?!0:void 0});if(i===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP){let h=this.activeSessions.get(o);h?.waitingForPromptResponse&&(h.waitingForPromptResponse=!1,h.pendingPromptId=void 0,h.pendingSubmitMap=void 0,n.info("Clearing prompt wait state - new desktop prompt received",{sessionId:o}))}n.debug("Event sent to AppSync successfully")}catch(o){throw n.error("Failed to process hook event:",o),o}}async handleSessionStart(e){let s=e.session_id,t=this.generateBackendSessionId(s),i=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(s,t),n.info("Session started",{claudeSessionId:s,sessionId:t,cwd:i});let r=Array.from(this.activeSessions.keys()).filter(c=>c!==t);if(r.length>0){n.info(`Marking ${r.length} previous session(s) as INACTIVE`);for(let c of r){try{await this.appSyncClient.updateSession({sessionId:c,status:a.SessionStatus.INACTIVE}),n.info("Previous session marked INACTIVE",{prevId:c,newSessionId:t})}catch(u){n.warn("Failed to mark previous session as INACTIVE",{prevId:c,error:u})}let d=this.activeSessions.get(c);d&&this.removePortFile(d.claudeSessionId),this.activeSessions.delete(c)}}this.writePortFile(s);let o=this.appSyncClient.getCurrentUserId(),p={sessionId:t,claudeSessionId:s,userId:o,projectPath:i,cwd:i,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,metadata:e.metadata||{}};this.activeSessions.set(t,p);try{let c=await(0,a.resumeOrCreateSession)({sessionId:t,userId:p.userId,agentType:a.AgentType.CLAUDE,projectPath:i,metadata:e.metadata||{}},this.appSyncClient,n);if(this.sessionKey=c.sessionKey,c.resumed&&!c.sessionKey){let d=await a.keychainManager.getDeviceId();n.error("Device key not found in session encryptedKeys",{sessionId:t,pluginDeviceId:d}),console.error(`
3
+ \u26A0\uFE0F E2E ENCRYPTION WARNING: Cannot decrypt this session!`),console.error(` Your device ID (${d.substring(0,8)}...) is not in session's encryption keys.`),console.error(" This happens if your device key was regenerated after the session was created."),console.error(` SOLUTION: Start a new Claude Code session instead of resuming this one.
4
+ `)}}catch(c){if(this.isSessionLimitExceeded(c)){this.displaySubscriptionLimitError(c,"session"),this.activeSessions.delete(t),this.removePortFile(s);return}n.error("Failed to create/resume session:",c)}this.subscribeToMobileEvents(t),this.appSyncClient.startHeartbeat(t)}async handleSessionEnd(e){let s=e.session_id,t=this.claudeToBackendSessionId.get(s)||this.generateBackendSessionId(s);n.info("Session ended",{claudeSessionId:s,sessionId:t,reason:e.metadata?.reason}),this.removePortFile(s);let i=this.activeSessions.get(t);if(i?.waitingForPromptResponse&&(n.info("Clearing prompt wait state - session ending",{sessionId:t}),i.waitingForPromptResponse=!1,i.pendingPromptId=void 0),this.appSyncClient.stopHeartbeat(t),i)try{await this.appSyncClient.updateSession({sessionId:t,status:a.SessionStatus.INACTIVE}),n.info("Session marked as INACTIVE in AppSync",{sessionId:t})}catch(r){n.warn("Failed to update session in AppSync:",r)}else n.warn("Cannot update session - session state not found",{sessionId:t});this.activeSessions.delete(t),this.claudeToBackendSessionId.delete(s),n.debug("Session cleanup completed",{sessionId:t})}async registerDeviceEncryptionKey(){try{let e=await a.keychainManager.getDeviceId(),s=await a.keychainManager.getDevicePublicKey(),t=a.keychainManager.getDevicePlatform(),i=a.keychainManager.getDeviceName();n.info("Registering device encryption key",{deviceId:e,platform:t,deviceName:i}),await this.appSyncClient.registerDeviceKey(e,s,t,i),a.keychainManager.setIsRegistered(!0),n.info("Device encryption key registered successfully",{deviceId:e})}catch(e){n.warn("Failed to register device encryption key (E2E encryption may not work):",e)}}subscribeToMobileEvents(e){n.info("Subscribing to mobile events",{sessionId:e});let s=this.activeSessions.get(e);if(!s){n.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async t=>{n.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let i=t.content||"";if(t.isEncrypted&&this.sessionKey)try{i=a.cryptoService.decryptContent(t.content,this.sessionKey),n.debug("Event decrypted successfully",{eventId:t.eventId})}catch(o){n.error("Failed to decrypt event:",{eventId:t.eventId,error:o}),i=t.content}let r={...t,content:i};try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:a.DeliveryStatus.DELIVERED}),n.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(o){n.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:o})}if(t.type===a.EventType.USER_PROMPT){let o=this.activeSessions.get(e);if(o?.waitingForPromptResponse){let p=i.trim(),c=o.pendingSubmitMap?Object.keys(o.pendingSubmitMap).length:3,d=this.parseInteractivePromptInput(p,c);if(n.info("Parsed interactive prompt input",{sessionId:e,content:p,parsed:d,hasSubmitMap:!!o.pendingSubmitMap}),d.action==="select_option"){let u=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option",{option:d.option,terminalInput:u}),await this.promptResponder.answerInteractivePrompt(e,u)?(await this.markEventExecuted(t),o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:`Selected option ${d.option}`,metadata:{promptAnswered:!0}})):await this.sendPromptError(e,"Failed to select option")}else if(d.action==="option_with_followup"){let u=o.pendingSubmitMap?.[d.option]||d.option;n.info("User selected option with follow-up",{option:d.option,terminalInput:u,followUpText:d.followUpText});let h=await this.promptResponder.answerInteractivePrompt(e,u);if(o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,h){if(await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:`Selected option ${d.option}`,metadata:{promptAnswered:!0}}),d.followUpText){await new Promise(f=>setTimeout(f,1e3));let g={...t,content:d.followUpText};await this.executeMobilePrompt(e,g)}await this.markEventExecuted(t)}else await this.sendPromptError(e,"Failed to select option")}else n.info("Sending as free-form response to interactive prompt",{response:p}),await this.promptResponder.answerInteractivePrompt(e,p)?(await this.markEventExecuted(t),o.waitingForPromptResponse=!1,o.pendingPromptId=void 0,o.pendingSubmitMap=void 0,await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Response sent to interactive prompt",metadata:{promptAnswered:!0}})):await this.sendPromptError(e,"Failed to send response")}else await this.executeMobilePrompt(e,r)}},t=>{n.error("Subscription error",{sessionId:e,error:t})}),s.subscriptionActive=!0,n.info("Subscription active",{sessionId:e})}async sendInteractivePromptAsync(e,s,t){await new Promise(u=>setTimeout(u,500));let i=process.env.CODEVIBE_TMUX_SESSION,r={...s.metadata||{}};if(i)try{let{exec:u}=await import("child_process"),h=x=>new Promise((V,q)=>{u(x,{timeout:5e3},(_,X)=>{_?q(_):V({stdout:X||""})})}),{stdout:g}=await h(`tmux capture-pane -p -e -S -30 -t '${i}'`),f=g.split(`
5
+ `);n.info("tmux capture result",{tmuxSession:i,totalLines:f.length,lastLines:f.slice(-15).map(x=>x.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let S=(0,a.parseInteractivePrompt)(g);S&&S.options.length>0?(r.options=S.options,r.submitMap=S.submitMap,r.instructions=this.buildPromptInstructions(S),n.info("Parsed dynamic options from tmux",{optionCount:S.options.length,kind:S.kind,options:S.options})):(n.info("No dynamic options parsed from tmux, using fallback",{parsedResult:S}),this.addFallbackOptions(r))}catch(u){n.warn("Failed to capture tmux pane for options",{error:u}),this.addFallbackOptions(r)}else n.warn("No tmux session \u2014 using fallback options"),this.addFallbackOptions(r);let o=this.activeSessions.get(e);o&&r.submitMap&&(o.pendingSubmitMap=r.submitMap);let p=t,c=r,d=!1;this.sessionKey&&(p=a.cryptoService.encryptContent(t,this.sessionKey),c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)},d=!0),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.INTERACTIVE_PROMPT,source:s.source,content:p,metadata:c,promptId:s.prompt_id,isEncrypted:d?!0:void 0}),n.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}addFallbackOptions(e){e.options=[{number:"1",text:"Yes"},{number:"2",text:"Yes, and don't ask again"},{number:"3",text:"Reject and tell Claude what to do differently"}],e.submitMap={1:"1",2:"2",3:"3"},e.instructions="Reply with 1, 2, or 3. Append a message to provide alternative instructions."}buildPromptInstructions(e){return`Reply with ${e.options.map(t=>t.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,s=3){return B(e,s)}async markEventExecuted(e){try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),n.info("Event marked as EXECUTED",{eventId:e.eventId})}catch(s){n.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:s})}}async sendPromptError(e,s){await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:s,metadata:{error:!0}})}isSessionLimitExceeded(e){return this.getErrorMessage(e).includes("SESSION_LIMIT_EXCEEDED")}isUsageLimitExceeded(e){let s=this.getErrorMessage(e);return s.includes("MESSAGE_LIMIT_EXCEEDED")||s.includes("IMAGE_LIMIT_EXCEEDED")}getErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){let s=e;if(s.errors&&Array.isArray(s.errors))return s.errors.map(t=>t.message||"").join(" ");if(typeof s.message=="string")return s.message}return String(e)}displaySubscriptionLimitError(e,s){let t=this.getErrorMessage(e),i="",r=t.match(/for your (\w+) plan/i);r&&(i=` (${r[1]} tier)`);let o="",p=t.match(/of (\d+)/);switch(p&&(o=` [Limit: ${p[1]}]`),console.log(`
6
+ `+"=".repeat(60)),console.log("\u26A0\uFE0F SUBSCRIPTION LIMIT REACHED"),console.log("=".repeat(60)),s){case"session":console.log(`You have reached the maximum number of active sessions${i}.`),console.log(`${o}`),console.log(`
7
+ To continue, please:`),console.log(" \u2022 Close an existing Claude Code session, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"message":console.log(`You have reached your monthly message limit${i}.`),console.log(`${o}`),console.log(`
8
+ To continue, please:`),console.log(" \u2022 Wait until your usage resets next month, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"image":console.log(`You have reached your monthly image attachment limit${i}.`),console.log(`${o}`),console.log(`
9
+ To continue, please:`),console.log(" \u2022 Wait until your usage resets next month, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break}console.log(`
10
+ Note: You can still use Claude Code normally from your desktop.`),console.log("This limit only affects syncing with the mobile app."),console.log("=".repeat(60)+`
11
+ `),n.error("Subscription limit exceeded",{limitType:s,errorMessage:t})}async downloadAttachment(e,s,t){try{let i=e.isEncrypted??t??!1;n.info("Downloading attachment - START",{id:e.id,type:e.type,filename:e.filename,s3Key:e.s3Key,attachmentIsEncrypted:e.isEncrypted,eventIsEncrypted:t,shouldDecrypt:i,hasSessionKey:!!this.sessionKey});let{downloadUrl:r}=await this.appSyncClient.getAttachmentDownloadUrl(e.s3Key),o=await fetch(r);if(!o.ok)throw new Error(`Failed to download attachment: ${o.status} ${o.statusText}`);let p=Buffer.from(await o.arrayBuffer());if(n.info("Attachment downloaded",{id:e.id,downloadedSize:p.length,first20Bytes:p.slice(0,20).toString("hex")}),n.info("Checking decryption conditions",{id:e.id,shouldDecrypt:i,hasSessionKey:!!this.sessionKey,willDecrypt:!!(i&&this.sessionKey)}),i&&this.sessionKey)try{n.info("Decrypting attachment",{id:e.id,encryptedSize:p.length}),p=a.cryptoService.decryptData(p,this.sessionKey),n.info("Attachment decrypted successfully",{id:e.id,decryptedSize:p.length,first20Bytes:p.slice(0,20).toString("hex")})}catch(f){throw n.error("Failed to decrypt attachment:",{id:e.id,error:f}),new Error("Failed to decrypt attachment")}else i&&!this.sessionKey?n.warn("Cannot decrypt attachment - no session key available",{id:e.id}):n.info("Skipping decryption - attachment not encrypted or no session key",{id:e.id,shouldDecrypt:i,hasSessionKey:!!this.sessionKey});let c=E.join(T.tmpdir(),"codevibe-claude",s);v.existsSync(c)||v.mkdirSync(c,{recursive:!0});let d="",u=e.filename;if(i&&e.filename&&this.sessionKey)try{u=a.cryptoService.decryptContent(e.filename,this.sessionKey)}catch{u=e.filename}if(u){let f=E.extname(u);f&&(d=f)}d||(d={"image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","image/heic":".heic","application/pdf":".pdf"}[e.type]||".bin");let h=`attachment-${e.id}${d}`,g=E.join(c,h);return v.writeFileSync(g,p),n.info("Attachment saved to temp file",{id:e.id,filePath:g,size:p.length,wasDecrypted:i&&!!this.sessionKey}),g}catch(i){return n.error("Failed to download attachment:",{id:e.id,error:i}),null}}async executeMobilePrompt(e,s){let t=s.content||"",i=s.attachments||[];n.info("Executing mobile prompt via tmux",{sessionId:e,promptLength:t.length,attachmentCount:i.length});let r=[];if(i.length>0){n.info("Downloading attachments for prompt",{count:i.length});for(let o of i){let p=await this.downloadAttachment(o,e,s.isEncrypted);p&&r.push(p)}if(r.length>0){let o=r.map(p=>`[Attached file: ${p}]`).join(`
12
+ `);t?t=`${o}
13
+
14
+ ${t}`:t=`${o}
15
+
16
+ Please analyze the attached file(s).`,n.info("Prompt updated with attachment paths",{attachmentCount:r.length,newPromptLength:t.length})}}this.trackMobilePrompt(e,t);try{if(await this.promptResponder.answerInteractivePrompt(e,t)){try{await this.appSyncClient.updateEventStatus({eventId:s.eventId,sessionId:s.sessionId,timestamp:s.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),n.info("Event marked as EXECUTED",{eventId:s.eventId})}catch(c){n.warn("Failed to mark event as EXECUTED",{eventId:s.eventId,error:c})}n.info("Mobile prompt sent successfully",{sessionId:e});let p=r.length>0?`Prompt with ${r.length} attachment(s) sent to Claude Code`:`Prompt "${t.substring(0,50)}${t.length>50?"...":""}" sent to Claude Code`;await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:p,metadata:{mobilePrompt:!0,attachmentCount:r.length}})}else n.error("Failed to send mobile prompt",{sessionId:e}),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Failed to send prompt to Claude Code",metadata:{error:!0}})}catch(o){n.error("Failed to execute mobile prompt:",o)}}};async function ee(){let m=process.argv[2]||process.env.CLAUDE_SESSION_ID;m?n.info(`Starting MCP server for session: ${m}`):n.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new A(m);try{await e.start();let s=e.getPort();console.log(`PORT=${s}`);let t=!1,i=async r=>{if(t){n.info("Shutdown already in progress, ignoring additional signal");return}t=!0,n.info(`Received ${r} signal, stopping server...`);try{await e.stop(),n.info("Graceful shutdown completed"),process.exit(0)}catch(o){n.error("Error during shutdown:",o),process.exit(1)}};process.on("SIGINT",()=>i("SIGINT")),process.on("SIGTERM",()=>i("SIGTERM")),process.on("SIGHUP",()=>i("SIGHUP")),process.on("uncaughtException",async r=>{n.error("Uncaught exception:",r),await i("uncaughtException")}),process.on("unhandledRejection",async r=>{n.error("Unhandled rejection:",r),await i("unhandledRejection")})}catch(s){n.error("Failed to start MCP Server:",s),process.exit(1)}}function B(m,e=3){let s=m.trim(),t=s.match(/^(\d+)$/);if(t){let r=parseInt(t[1]);if(r>=1&&r<=e)return{action:"select_option",option:t[1]}}let i=s.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);if(i){let r=parseInt(i[1]);if(r>=1&&r<=e)return{action:"option_with_followup",option:i[1],followUpText:i[2].trim()}}return{action:"send_as_response"}}ee().catch(m=>{n.error("Unhandled error in main:",m),process.exit(1)});0&&(module.exports={parseInteractivePromptInput});