@pome-sh/cli 0.1.0 → 0.1.1

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 (471) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/src/doctor/checks.js +2 -2
  3. package/node_modules/@pome-sh/sdk/dist/db.d.ts +4 -4
  4. package/node_modules/@pome-sh/sdk/dist/db.js +93 -25
  5. package/node_modules/@pome-sh/sdk/dist/db.js.map +1 -1
  6. package/node_modules/@pome-sh/sdk/package.json +2 -6
  7. package/node_modules/@pome-sh/twin-github/README.md +2 -1
  8. package/node_modules/@pome-sh/twin-github/package.json +2 -3
  9. package/node_modules/@pome-sh/twin-slack/package.json +2 -3
  10. package/node_modules/@pome-sh/twin-stripe/README.md +2 -2
  11. package/node_modules/@pome-sh/twin-stripe/package.json +2 -3
  12. package/package.json +6 -7
  13. package/dist/src/recorder/verdictArtifact.d.ts +0 -71
  14. package/dist/src/recorder/verdictArtifact.js +0 -207
  15. package/dist/src/score/view.d.ts +0 -38
  16. package/dist/src/score/view.js +0 -59
  17. package/dist/src/twin/github/app.d.ts +0 -10
  18. package/dist/src/twin/github/app.js +0 -316
  19. package/dist/src/twin/github/db/migrations.d.ts +0 -2
  20. package/dist/src/twin/github/db/migrations.js +0 -64
  21. package/dist/src/twin/github/db/openDatabase.d.ts +0 -2
  22. package/dist/src/twin/github/db/openDatabase.js +0 -13
  23. package/dist/src/twin/github/db/queries.d.ts +0 -77
  24. package/dist/src/twin/github/db/queries.js +0 -93
  25. package/dist/src/twin/github/domain/seed.d.ts +0 -74
  26. package/dist/src/twin/github/domain/seed.js +0 -104
  27. package/dist/src/twin/github/domain/serializers.d.ts +0 -113
  28. package/dist/src/twin/github/domain/serializers.js +0 -76
  29. package/dist/src/twin/github/types.d.ts +0 -66
  30. package/dist/src/twin/github/types.js +0 -1
  31. package/dist/src/twin-github/app.d.ts +0 -9
  32. package/dist/src/twin-github/app.js +0 -327
  33. package/dist/src/twin-github/auth.d.ts +0 -16
  34. package/dist/src/twin-github/auth.js +0 -142
  35. package/dist/src/twin-github/build-info.d.ts +0 -7
  36. package/dist/src/twin-github/build-info.js +0 -52
  37. package/dist/src/twin-github/db.d.ts +0 -4
  38. package/dist/src/twin-github/db.js +0 -362
  39. package/dist/src/twin-github/domain.d.ts +0 -1649
  40. package/dist/src/twin-github/domain.js +0 -957
  41. package/dist/src/twin-github/errors.d.ts +0 -14
  42. package/dist/src/twin-github/errors.js +0 -34
  43. package/dist/src/twin-github/index.d.ts +0 -9
  44. package/dist/src/twin-github/index.js +0 -7
  45. package/dist/src/twin-github/mcp.d.ts +0 -17
  46. package/dist/src/twin-github/mcp.js +0 -281
  47. package/dist/src/twin-github/recorder.d.ts +0 -2
  48. package/dist/src/twin-github/recorder.js +0 -29
  49. package/dist/src/twin-github/seed.d.ts +0 -74
  50. package/dist/src/twin-github/seed.js +0 -107
  51. package/dist/src/twin-github/serializers.d.ts +0 -449
  52. package/dist/src/twin-github/serializers.js +0 -429
  53. package/dist/src/twin-github/server.d.ts +0 -1
  54. package/dist/src/twin-github/server.js +0 -34
  55. package/dist/src/twin-github/tools.d.ts +0 -1545
  56. package/dist/src/twin-github/tools.js +0 -347
  57. package/dist/src/twin-github/types.d.ts +0 -198
  58. package/dist/src/twin-github/types.js +0 -1
  59. package/dist/src/twin-github/unsupported-envelope.d.ts +0 -10
  60. package/dist/src/twin-github/unsupported-envelope.js +0 -29
  61. package/dist/src/twin-github/util.d.ts +0 -13
  62. package/dist/src/twin-github/util.js +0 -61
  63. package/dist/src/twin-slack/app.d.ts +0 -17
  64. package/dist/src/twin-slack/app.js +0 -683
  65. package/dist/src/twin-slack/auth.d.ts +0 -38
  66. package/dist/src/twin-slack/auth.js +0 -219
  67. package/dist/src/twin-slack/build-info.d.ts +0 -7
  68. package/dist/src/twin-slack/build-info.js +0 -9
  69. package/dist/src/twin-slack/db.d.ts +0 -4
  70. package/dist/src/twin-slack/db.js +0 -213
  71. package/dist/src/twin-slack/domain.d.ts +0 -236
  72. package/dist/src/twin-slack/domain.js +0 -1293
  73. package/dist/src/twin-slack/errors.d.ts +0 -16
  74. package/dist/src/twin-slack/errors.js +0 -60
  75. package/dist/src/twin-slack/index.d.ts +0 -8
  76. package/dist/src/twin-slack/index.js +0 -8
  77. package/dist/src/twin-slack/mcp.d.ts +0 -17
  78. package/dist/src/twin-slack/mcp.js +0 -226
  79. package/dist/src/twin-slack/recorder.d.ts +0 -2
  80. package/dist/src/twin-slack/recorder.js +0 -15
  81. package/dist/src/twin-slack/seed.d.ts +0 -49
  82. package/dist/src/twin-slack/seed.js +0 -139
  83. package/dist/src/twin-slack/serializers.d.ts +0 -141
  84. package/dist/src/twin-slack/serializers.js +0 -377
  85. package/dist/src/twin-slack/tools.d.ts +0 -97
  86. package/dist/src/twin-slack/tools.js +0 -171
  87. package/dist/src/twin-slack/types.d.ts +0 -164
  88. package/dist/src/twin-slack/types.js +0 -1
  89. package/dist/src/twin-slack/unsupported-envelope.d.ts +0 -12
  90. package/dist/src/twin-slack/unsupported-envelope.js +0 -61
  91. package/dist/src/twin-slack/upstream-types.d.ts +0 -19
  92. package/dist/src/twin-slack/upstream-types.js +0 -1
  93. package/dist/src/twin-slack/util.d.ts +0 -31
  94. package/dist/src/twin-slack/util.js +0 -136
  95. package/node_modules/base64-js/LICENSE +0 -21
  96. package/node_modules/base64-js/README.md +0 -34
  97. package/node_modules/base64-js/base64js.min.js +0 -1
  98. package/node_modules/base64-js/index.d.ts +0 -3
  99. package/node_modules/base64-js/index.js +0 -150
  100. package/node_modules/base64-js/package.json +0 -47
  101. package/node_modules/better-sqlite3/LICENSE +0 -21
  102. package/node_modules/better-sqlite3/README.md +0 -99
  103. package/node_modules/better-sqlite3/binding.gyp +0 -38
  104. package/node_modules/better-sqlite3/build/Release/better_sqlite3.node +0 -0
  105. package/node_modules/better-sqlite3/deps/common.gypi +0 -68
  106. package/node_modules/better-sqlite3/deps/copy.js +0 -31
  107. package/node_modules/better-sqlite3/deps/defines.gypi +0 -42
  108. package/node_modules/better-sqlite3/deps/download.sh +0 -123
  109. package/node_modules/better-sqlite3/deps/patches/1208.patch +0 -15
  110. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3.c +0 -269395
  111. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3.h +0 -14347
  112. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3ext.h +0 -739
  113. package/node_modules/better-sqlite3/deps/sqlite3.gyp +0 -80
  114. package/node_modules/better-sqlite3/deps/test_extension.c +0 -21
  115. package/node_modules/better-sqlite3/lib/database.js +0 -90
  116. package/node_modules/better-sqlite3/lib/index.js +0 -3
  117. package/node_modules/better-sqlite3/lib/methods/aggregate.js +0 -43
  118. package/node_modules/better-sqlite3/lib/methods/backup.js +0 -67
  119. package/node_modules/better-sqlite3/lib/methods/function.js +0 -31
  120. package/node_modules/better-sqlite3/lib/methods/inspect.js +0 -7
  121. package/node_modules/better-sqlite3/lib/methods/pragma.js +0 -12
  122. package/node_modules/better-sqlite3/lib/methods/serialize.js +0 -16
  123. package/node_modules/better-sqlite3/lib/methods/table.js +0 -189
  124. package/node_modules/better-sqlite3/lib/methods/transaction.js +0 -78
  125. package/node_modules/better-sqlite3/lib/methods/wrappers.js +0 -54
  126. package/node_modules/better-sqlite3/lib/sqlite-error.js +0 -20
  127. package/node_modules/better-sqlite3/lib/util.js +0 -12
  128. package/node_modules/better-sqlite3/package.json +0 -59
  129. package/node_modules/better-sqlite3/src/addon.cpp +0 -47
  130. package/node_modules/better-sqlite3/src/better_sqlite3.cpp +0 -74
  131. package/node_modules/better-sqlite3/src/objects/backup.cpp +0 -120
  132. package/node_modules/better-sqlite3/src/objects/backup.hpp +0 -36
  133. package/node_modules/better-sqlite3/src/objects/database.cpp +0 -417
  134. package/node_modules/better-sqlite3/src/objects/database.hpp +0 -103
  135. package/node_modules/better-sqlite3/src/objects/statement-iterator.cpp +0 -113
  136. package/node_modules/better-sqlite3/src/objects/statement-iterator.hpp +0 -50
  137. package/node_modules/better-sqlite3/src/objects/statement.cpp +0 -383
  138. package/node_modules/better-sqlite3/src/objects/statement.hpp +0 -58
  139. package/node_modules/better-sqlite3/src/util/bind-map.cpp +0 -73
  140. package/node_modules/better-sqlite3/src/util/binder.cpp +0 -193
  141. package/node_modules/better-sqlite3/src/util/constants.cpp +0 -172
  142. package/node_modules/better-sqlite3/src/util/custom-aggregate.cpp +0 -121
  143. package/node_modules/better-sqlite3/src/util/custom-function.cpp +0 -59
  144. package/node_modules/better-sqlite3/src/util/custom-table.cpp +0 -409
  145. package/node_modules/better-sqlite3/src/util/data-converter.cpp +0 -17
  146. package/node_modules/better-sqlite3/src/util/data.cpp +0 -194
  147. package/node_modules/better-sqlite3/src/util/helpers.cpp +0 -109
  148. package/node_modules/better-sqlite3/src/util/macros.cpp +0 -90
  149. package/node_modules/better-sqlite3/src/util/query-macros.cpp +0 -71
  150. package/node_modules/better-sqlite3/src/util/row-builder.cpp +0 -49
  151. package/node_modules/bindings/LICENSE.md +0 -22
  152. package/node_modules/bindings/README.md +0 -98
  153. package/node_modules/bindings/bindings.js +0 -221
  154. package/node_modules/bindings/package.json +0 -28
  155. package/node_modules/bl/.travis.yml +0 -17
  156. package/node_modules/bl/BufferList.js +0 -396
  157. package/node_modules/bl/LICENSE.md +0 -13
  158. package/node_modules/bl/README.md +0 -247
  159. package/node_modules/bl/bl.js +0 -84
  160. package/node_modules/bl/package.json +0 -37
  161. package/node_modules/bl/test/convert.js +0 -21
  162. package/node_modules/bl/test/indexOf.js +0 -492
  163. package/node_modules/bl/test/isBufferList.js +0 -32
  164. package/node_modules/bl/test/test.js +0 -869
  165. package/node_modules/buffer/AUTHORS.md +0 -70
  166. package/node_modules/buffer/LICENSE +0 -21
  167. package/node_modules/buffer/README.md +0 -410
  168. package/node_modules/buffer/index.d.ts +0 -186
  169. package/node_modules/buffer/index.js +0 -1817
  170. package/node_modules/buffer/package.json +0 -96
  171. package/node_modules/chownr/LICENSE +0 -15
  172. package/node_modules/chownr/README.md +0 -3
  173. package/node_modules/chownr/chownr.js +0 -167
  174. package/node_modules/chownr/package.json +0 -29
  175. package/node_modules/decompress-response/index.d.ts +0 -22
  176. package/node_modules/decompress-response/index.js +0 -58
  177. package/node_modules/decompress-response/license +0 -9
  178. package/node_modules/decompress-response/package.json +0 -56
  179. package/node_modules/decompress-response/readme.md +0 -48
  180. package/node_modules/deep-extend/CHANGELOG.md +0 -46
  181. package/node_modules/deep-extend/LICENSE +0 -20
  182. package/node_modules/deep-extend/README.md +0 -91
  183. package/node_modules/deep-extend/index.js +0 -1
  184. package/node_modules/deep-extend/lib/deep-extend.js +0 -150
  185. package/node_modules/deep-extend/package.json +0 -62
  186. package/node_modules/detect-libc/LICENSE +0 -201
  187. package/node_modules/detect-libc/README.md +0 -163
  188. package/node_modules/detect-libc/index.d.ts +0 -14
  189. package/node_modules/detect-libc/lib/detect-libc.js +0 -313
  190. package/node_modules/detect-libc/lib/elf.js +0 -39
  191. package/node_modules/detect-libc/lib/filesystem.js +0 -51
  192. package/node_modules/detect-libc/lib/process.js +0 -24
  193. package/node_modules/detect-libc/package.json +0 -44
  194. package/node_modules/end-of-stream/LICENSE +0 -21
  195. package/node_modules/end-of-stream/README.md +0 -54
  196. package/node_modules/end-of-stream/index.js +0 -96
  197. package/node_modules/end-of-stream/package.json +0 -37
  198. package/node_modules/expand-template/.travis.yml +0 -6
  199. package/node_modules/expand-template/LICENSE +0 -21
  200. package/node_modules/expand-template/README.md +0 -43
  201. package/node_modules/expand-template/index.js +0 -26
  202. package/node_modules/expand-template/package.json +0 -29
  203. package/node_modules/expand-template/test.js +0 -67
  204. package/node_modules/file-uri-to-path/.npmignore +0 -1
  205. package/node_modules/file-uri-to-path/.travis.yml +0 -30
  206. package/node_modules/file-uri-to-path/History.md +0 -21
  207. package/node_modules/file-uri-to-path/LICENSE +0 -20
  208. package/node_modules/file-uri-to-path/README.md +0 -74
  209. package/node_modules/file-uri-to-path/index.d.ts +0 -2
  210. package/node_modules/file-uri-to-path/index.js +0 -66
  211. package/node_modules/file-uri-to-path/package.json +0 -32
  212. package/node_modules/file-uri-to-path/test/test.js +0 -24
  213. package/node_modules/file-uri-to-path/test/tests.json +0 -13
  214. package/node_modules/fs-constants/LICENSE +0 -21
  215. package/node_modules/fs-constants/README.md +0 -26
  216. package/node_modules/fs-constants/browser.js +0 -1
  217. package/node_modules/fs-constants/index.js +0 -1
  218. package/node_modules/fs-constants/package.json +0 -19
  219. package/node_modules/github-from-package/.travis.yml +0 -4
  220. package/node_modules/github-from-package/LICENSE +0 -18
  221. package/node_modules/github-from-package/example/package.json +0 -8
  222. package/node_modules/github-from-package/example/url.js +0 -3
  223. package/node_modules/github-from-package/index.js +0 -17
  224. package/node_modules/github-from-package/package.json +0 -30
  225. package/node_modules/github-from-package/readme.markdown +0 -53
  226. package/node_modules/github-from-package/test/a.json +0 -8
  227. package/node_modules/github-from-package/test/b.json +0 -5
  228. package/node_modules/github-from-package/test/c.json +0 -5
  229. package/node_modules/github-from-package/test/d.json +0 -7
  230. package/node_modules/github-from-package/test/e.json +0 -5
  231. package/node_modules/github-from-package/test/url.js +0 -19
  232. package/node_modules/ieee754/LICENSE +0 -11
  233. package/node_modules/ieee754/README.md +0 -51
  234. package/node_modules/ieee754/index.d.ts +0 -10
  235. package/node_modules/ieee754/index.js +0 -85
  236. package/node_modules/ieee754/package.json +0 -52
  237. package/node_modules/inherits/LICENSE +0 -16
  238. package/node_modules/inherits/README.md +0 -42
  239. package/node_modules/inherits/inherits.js +0 -9
  240. package/node_modules/inherits/inherits_browser.js +0 -27
  241. package/node_modules/inherits/package.json +0 -29
  242. package/node_modules/ini/LICENSE +0 -15
  243. package/node_modules/ini/README.md +0 -102
  244. package/node_modules/ini/ini.js +0 -206
  245. package/node_modules/ini/package.json +0 -33
  246. package/node_modules/mimic-response/index.d.ts +0 -17
  247. package/node_modules/mimic-response/index.js +0 -77
  248. package/node_modules/mimic-response/license +0 -9
  249. package/node_modules/mimic-response/package.json +0 -42
  250. package/node_modules/mimic-response/readme.md +0 -78
  251. package/node_modules/minimist/.eslintrc +0 -29
  252. package/node_modules/minimist/.github/FUNDING.yml +0 -12
  253. package/node_modules/minimist/.nycrc +0 -14
  254. package/node_modules/minimist/CHANGELOG.md +0 -298
  255. package/node_modules/minimist/LICENSE +0 -18
  256. package/node_modules/minimist/README.md +0 -121
  257. package/node_modules/minimist/example/parse.js +0 -4
  258. package/node_modules/minimist/index.js +0 -263
  259. package/node_modules/minimist/package.json +0 -75
  260. package/node_modules/minimist/test/all_bool.js +0 -34
  261. package/node_modules/minimist/test/bool.js +0 -177
  262. package/node_modules/minimist/test/dash.js +0 -43
  263. package/node_modules/minimist/test/default_bool.js +0 -37
  264. package/node_modules/minimist/test/dotted.js +0 -24
  265. package/node_modules/minimist/test/kv_short.js +0 -32
  266. package/node_modules/minimist/test/long.js +0 -33
  267. package/node_modules/minimist/test/num.js +0 -38
  268. package/node_modules/minimist/test/parse.js +0 -209
  269. package/node_modules/minimist/test/parse_modified.js +0 -11
  270. package/node_modules/minimist/test/proto.js +0 -64
  271. package/node_modules/minimist/test/short.js +0 -69
  272. package/node_modules/minimist/test/stop_early.js +0 -17
  273. package/node_modules/minimist/test/unknown.js +0 -104
  274. package/node_modules/minimist/test/whitespace.js +0 -10
  275. package/node_modules/mkdirp-classic/LICENSE +0 -21
  276. package/node_modules/mkdirp-classic/README.md +0 -18
  277. package/node_modules/mkdirp-classic/index.js +0 -98
  278. package/node_modules/mkdirp-classic/package.json +0 -18
  279. package/node_modules/napi-build-utils/.github/workflows/run-npm-tests.yml +0 -31
  280. package/node_modules/napi-build-utils/LICENSE +0 -21
  281. package/node_modules/napi-build-utils/README.md +0 -52
  282. package/node_modules/napi-build-utils/index.js +0 -214
  283. package/node_modules/napi-build-utils/index.md +0 -0
  284. package/node_modules/napi-build-utils/package.json +0 -42
  285. package/node_modules/node-abi/LICENSE +0 -21
  286. package/node_modules/node-abi/README.md +0 -54
  287. package/node_modules/node-abi/abi_registry.json +0 -453
  288. package/node_modules/node-abi/index.js +0 -179
  289. package/node_modules/node-abi/package.json +0 -45
  290. package/node_modules/once/LICENSE +0 -15
  291. package/node_modules/once/README.md +0 -79
  292. package/node_modules/once/once.js +0 -42
  293. package/node_modules/once/package.json +0 -33
  294. package/node_modules/prebuild-install/CHANGELOG.md +0 -131
  295. package/node_modules/prebuild-install/CONTRIBUTING.md +0 -6
  296. package/node_modules/prebuild-install/LICENSE +0 -21
  297. package/node_modules/prebuild-install/README.md +0 -163
  298. package/node_modules/prebuild-install/asset.js +0 -44
  299. package/node_modules/prebuild-install/bin.js +0 -78
  300. package/node_modules/prebuild-install/download.js +0 -142
  301. package/node_modules/prebuild-install/error.js +0 -14
  302. package/node_modules/prebuild-install/help.txt +0 -16
  303. package/node_modules/prebuild-install/index.js +0 -1
  304. package/node_modules/prebuild-install/log.js +0 -33
  305. package/node_modules/prebuild-install/package.json +0 -67
  306. package/node_modules/prebuild-install/proxy.js +0 -35
  307. package/node_modules/prebuild-install/rc.js +0 -64
  308. package/node_modules/prebuild-install/util.js +0 -143
  309. package/node_modules/pump/.github/FUNDING.yml +0 -2
  310. package/node_modules/pump/.travis.yml +0 -5
  311. package/node_modules/pump/LICENSE +0 -21
  312. package/node_modules/pump/README.md +0 -74
  313. package/node_modules/pump/SECURITY.md +0 -5
  314. package/node_modules/pump/empty.js +0 -1
  315. package/node_modules/pump/index.js +0 -86
  316. package/node_modules/pump/package.json +0 -30
  317. package/node_modules/pump/test-browser.js +0 -66
  318. package/node_modules/pump/test-node.js +0 -53
  319. package/node_modules/rc/LICENSE.APACHE2 +0 -15
  320. package/node_modules/rc/LICENSE.BSD +0 -26
  321. package/node_modules/rc/LICENSE.MIT +0 -24
  322. package/node_modules/rc/README.md +0 -227
  323. package/node_modules/rc/browser.js +0 -7
  324. package/node_modules/rc/cli.js +0 -4
  325. package/node_modules/rc/index.js +0 -53
  326. package/node_modules/rc/lib/utils.js +0 -104
  327. package/node_modules/rc/package.json +0 -29
  328. package/node_modules/rc/test/ini.js +0 -16
  329. package/node_modules/rc/test/nested-env-vars.js +0 -50
  330. package/node_modules/rc/test/test.js +0 -59
  331. package/node_modules/readable-stream/CONTRIBUTING.md +0 -38
  332. package/node_modules/readable-stream/GOVERNANCE.md +0 -136
  333. package/node_modules/readable-stream/LICENSE +0 -47
  334. package/node_modules/readable-stream/README.md +0 -106
  335. package/node_modules/readable-stream/errors-browser.js +0 -127
  336. package/node_modules/readable-stream/errors.js +0 -116
  337. package/node_modules/readable-stream/experimentalWarning.js +0 -17
  338. package/node_modules/readable-stream/lib/_stream_duplex.js +0 -126
  339. package/node_modules/readable-stream/lib/_stream_passthrough.js +0 -37
  340. package/node_modules/readable-stream/lib/_stream_readable.js +0 -1027
  341. package/node_modules/readable-stream/lib/_stream_transform.js +0 -190
  342. package/node_modules/readable-stream/lib/_stream_writable.js +0 -641
  343. package/node_modules/readable-stream/lib/internal/streams/async_iterator.js +0 -180
  344. package/node_modules/readable-stream/lib/internal/streams/buffer_list.js +0 -183
  345. package/node_modules/readable-stream/lib/internal/streams/destroy.js +0 -96
  346. package/node_modules/readable-stream/lib/internal/streams/end-of-stream.js +0 -86
  347. package/node_modules/readable-stream/lib/internal/streams/from-browser.js +0 -3
  348. package/node_modules/readable-stream/lib/internal/streams/from.js +0 -52
  349. package/node_modules/readable-stream/lib/internal/streams/pipeline.js +0 -86
  350. package/node_modules/readable-stream/lib/internal/streams/state.js +0 -22
  351. package/node_modules/readable-stream/lib/internal/streams/stream-browser.js +0 -1
  352. package/node_modules/readable-stream/lib/internal/streams/stream.js +0 -1
  353. package/node_modules/readable-stream/package.json +0 -68
  354. package/node_modules/readable-stream/readable-browser.js +0 -9
  355. package/node_modules/readable-stream/readable.js +0 -16
  356. package/node_modules/safe-buffer/LICENSE +0 -21
  357. package/node_modules/safe-buffer/README.md +0 -584
  358. package/node_modules/safe-buffer/index.d.ts +0 -187
  359. package/node_modules/safe-buffer/index.js +0 -65
  360. package/node_modules/safe-buffer/package.json +0 -51
  361. package/node_modules/semver/LICENSE +0 -15
  362. package/node_modules/semver/README.md +0 -680
  363. package/node_modules/semver/bin/semver.js +0 -195
  364. package/node_modules/semver/classes/comparator.js +0 -143
  365. package/node_modules/semver/classes/index.js +0 -7
  366. package/node_modules/semver/classes/range.js +0 -577
  367. package/node_modules/semver/classes/semver.js +0 -350
  368. package/node_modules/semver/functions/clean.js +0 -8
  369. package/node_modules/semver/functions/cmp.js +0 -54
  370. package/node_modules/semver/functions/coerce.js +0 -62
  371. package/node_modules/semver/functions/compare-build.js +0 -9
  372. package/node_modules/semver/functions/compare-loose.js +0 -5
  373. package/node_modules/semver/functions/compare.js +0 -7
  374. package/node_modules/semver/functions/diff.js +0 -60
  375. package/node_modules/semver/functions/eq.js +0 -5
  376. package/node_modules/semver/functions/gt.js +0 -5
  377. package/node_modules/semver/functions/gte.js +0 -5
  378. package/node_modules/semver/functions/inc.js +0 -21
  379. package/node_modules/semver/functions/lt.js +0 -5
  380. package/node_modules/semver/functions/lte.js +0 -5
  381. package/node_modules/semver/functions/major.js +0 -5
  382. package/node_modules/semver/functions/minor.js +0 -5
  383. package/node_modules/semver/functions/neq.js +0 -5
  384. package/node_modules/semver/functions/parse.js +0 -18
  385. package/node_modules/semver/functions/patch.js +0 -5
  386. package/node_modules/semver/functions/prerelease.js +0 -8
  387. package/node_modules/semver/functions/rcompare.js +0 -5
  388. package/node_modules/semver/functions/rsort.js +0 -5
  389. package/node_modules/semver/functions/satisfies.js +0 -12
  390. package/node_modules/semver/functions/sort.js +0 -5
  391. package/node_modules/semver/functions/truncate.js +0 -48
  392. package/node_modules/semver/functions/valid.js +0 -8
  393. package/node_modules/semver/index.js +0 -93
  394. package/node_modules/semver/internal/constants.js +0 -37
  395. package/node_modules/semver/internal/debug.js +0 -11
  396. package/node_modules/semver/internal/identifiers.js +0 -29
  397. package/node_modules/semver/internal/lrucache.js +0 -42
  398. package/node_modules/semver/internal/parse-options.js +0 -17
  399. package/node_modules/semver/internal/re.js +0 -223
  400. package/node_modules/semver/package.json +0 -78
  401. package/node_modules/semver/preload.js +0 -4
  402. package/node_modules/semver/range.bnf +0 -17
  403. package/node_modules/semver/ranges/gtr.js +0 -6
  404. package/node_modules/semver/ranges/intersects.js +0 -9
  405. package/node_modules/semver/ranges/ltr.js +0 -6
  406. package/node_modules/semver/ranges/max-satisfying.js +0 -27
  407. package/node_modules/semver/ranges/min-satisfying.js +0 -26
  408. package/node_modules/semver/ranges/min-version.js +0 -63
  409. package/node_modules/semver/ranges/outside.js +0 -82
  410. package/node_modules/semver/ranges/simplify.js +0 -49
  411. package/node_modules/semver/ranges/subset.js +0 -249
  412. package/node_modules/semver/ranges/to-comparators.js +0 -10
  413. package/node_modules/semver/ranges/valid.js +0 -13
  414. package/node_modules/simple-concat/.travis.yml +0 -3
  415. package/node_modules/simple-concat/LICENSE +0 -20
  416. package/node_modules/simple-concat/README.md +0 -44
  417. package/node_modules/simple-concat/index.js +0 -15
  418. package/node_modules/simple-concat/package.json +0 -47
  419. package/node_modules/simple-concat/test/basic.js +0 -41
  420. package/node_modules/simple-get/.github/dependabot.yml +0 -15
  421. package/node_modules/simple-get/.github/workflows/ci.yml +0 -23
  422. package/node_modules/simple-get/LICENSE +0 -20
  423. package/node_modules/simple-get/README.md +0 -333
  424. package/node_modules/simple-get/index.js +0 -108
  425. package/node_modules/simple-get/package.json +0 -67
  426. package/node_modules/string_decoder/LICENSE +0 -48
  427. package/node_modules/string_decoder/README.md +0 -47
  428. package/node_modules/string_decoder/lib/string_decoder.js +0 -296
  429. package/node_modules/string_decoder/package.json +0 -34
  430. package/node_modules/strip-json-comments/index.js +0 -70
  431. package/node_modules/strip-json-comments/license +0 -21
  432. package/node_modules/strip-json-comments/package.json +0 -42
  433. package/node_modules/strip-json-comments/readme.md +0 -64
  434. package/node_modules/tar-fs/.travis.yml +0 -6
  435. package/node_modules/tar-fs/LICENSE +0 -21
  436. package/node_modules/tar-fs/README.md +0 -165
  437. package/node_modules/tar-fs/index.js +0 -392
  438. package/node_modules/tar-fs/package.json +0 -46
  439. package/node_modules/tar-fs/test/fixtures/a/hello.txt +0 -1
  440. package/node_modules/tar-fs/test/fixtures/b/a/test.txt +0 -1
  441. package/node_modules/tar-fs/test/fixtures/d/file1 +0 -0
  442. package/node_modules/tar-fs/test/fixtures/d/file2 +0 -0
  443. package/node_modules/tar-fs/test/fixtures/d/sub-dir/file5 +0 -0
  444. package/node_modules/tar-fs/test/fixtures/d/sub-files/file3 +0 -0
  445. package/node_modules/tar-fs/test/fixtures/d/sub-files/file4 +0 -0
  446. package/node_modules/tar-fs/test/fixtures/e/directory/.ignore +0 -0
  447. package/node_modules/tar-fs/test/fixtures/e/file +0 -0
  448. package/node_modules/tar-fs/test/fixtures/invalid.tar +0 -0
  449. package/node_modules/tar-fs/test/index.js +0 -457
  450. package/node_modules/tar-stream/LICENSE +0 -21
  451. package/node_modules/tar-stream/README.md +0 -168
  452. package/node_modules/tar-stream/extract.js +0 -257
  453. package/node_modules/tar-stream/headers.js +0 -295
  454. package/node_modules/tar-stream/index.js +0 -2
  455. package/node_modules/tar-stream/pack.js +0 -255
  456. package/node_modules/tar-stream/package.json +0 -58
  457. package/node_modules/tar-stream/sandbox.js +0 -11
  458. package/node_modules/tunnel-agent/LICENSE +0 -55
  459. package/node_modules/tunnel-agent/README.md +0 -4
  460. package/node_modules/tunnel-agent/index.js +0 -244
  461. package/node_modules/tunnel-agent/package.json +0 -22
  462. package/node_modules/util-deprecate/History.md +0 -16
  463. package/node_modules/util-deprecate/LICENSE +0 -24
  464. package/node_modules/util-deprecate/README.md +0 -53
  465. package/node_modules/util-deprecate/browser.js +0 -67
  466. package/node_modules/util-deprecate/node.js +0 -6
  467. package/node_modules/util-deprecate/package.json +0 -27
  468. package/node_modules/wrappy/LICENSE +0 -15
  469. package/node_modules/wrappy/README.md +0 -36
  470. package/node_modules/wrappy/package.json +0 -29
  471. package/node_modules/wrappy/wrappy.js +0 -33
@@ -1,1293 +0,0 @@
1
- import { resetDatabase } from "./db.js";
2
- import { slackError, notFound } from "./errors.js";
3
- import { parseSeed } from "./seed.js";
4
- import { SLACK_TWIN_HOST, serializeBookmark, serializeChannel, serializeFile, serializeMessage, serializePin, serializeUser, serializeUserProfile, serializeWorkspace, } from "./serializers.js";
5
- import { cursorDecode, cursorEncode, nowIso, nowUnix, padTsCounter, tsBaseSeconds } from "./util.js";
6
- const NOOP = () => { };
7
- const DEFAULT_BOT_USER = "U_PRIMARY";
8
- const DEFAULT_TEAM_ID = "T_POME";
9
- export class SlackDomain {
10
- db;
11
- constructor(db) {
12
- this.db = db;
13
- }
14
- // ───────────────────────────────────────────────────────────────────────────
15
- // Lifecycle
16
- // ───────────────────────────────────────────────────────────────────────────
17
- seed(state) {
18
- resetDatabase(this.db);
19
- const team = state.team ?? {};
20
- const teamId = team.id ?? DEFAULT_TEAM_ID;
21
- const teamName = team.name ?? "Pome Twin Workspace";
22
- const teamDomain = team.domain ?? "pome-twin";
23
- const now = nowIso();
24
- this.db.transaction(() => {
25
- this.db
26
- .prepare(`INSERT INTO workspaces (id, name, domain, url, enterprise_id, created_at, entity_counter)
27
- VALUES (?, ?, ?, ?, NULL, ?, 0)`)
28
- .run(teamId, teamName, teamDomain, `${SLACK_TWIN_HOST}/`, now);
29
- // Pre-create users from seed.
30
- const userIdByHandle = new Map();
31
- const seedUsers = state.users ?? [];
32
- for (const u of seedUsers) {
33
- const id = u.id ?? this.allocId(teamId, u.is_bot ? "B" : "U");
34
- const handle = u.name;
35
- userIdByHandle.set(handle, id);
36
- userIdByHandle.set(id, id);
37
- this.db
38
- .prepare(`INSERT INTO users (id, team_id, name, real_name, display_name, email, is_bot, is_admin, deleted, tz, profile_json, created_at, updated_at)
39
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)`)
40
- .run(id, teamId, handle, u.real_name ?? "", "", u.email ?? null, u.is_bot ? 1 : 0, u.is_admin ? 1 : 0, u.tz ?? "America/Los_Angeles", JSON.stringify(u.profile ?? {}), now, now);
41
- }
42
- // Ensure the pome-agent user always exists.
43
- if (!userIdByHandle.has("pome-agent") && !userIdByHandle.has(DEFAULT_BOT_USER)) {
44
- const id = DEFAULT_BOT_USER;
45
- userIdByHandle.set("pome-agent", id);
46
- userIdByHandle.set(id, id);
47
- this.db
48
- .prepare(`INSERT INTO users (id, team_id, name, real_name, display_name, email, is_bot, is_admin, deleted, tz, profile_json, created_at, updated_at)
49
- VALUES (?, ?, 'pome-agent', 'Pome Agent', '', 'pome-agent@pome-twin.slack.com', 0, 1, 0, 'America/Los_Angeles', '{}', ?, ?)`)
50
- .run(id, teamId, now, now);
51
- }
52
- // Channels + members + messages.
53
- const seedChannels = state.channels ?? [];
54
- for (const ch of seedChannels) {
55
- const channelId = ch.id ?? this.allocId(teamId, ch.is_private ? "G" : "C");
56
- const creator = this.resolveSeedUserId(ch.creator, userIdByHandle) ?? DEFAULT_BOT_USER;
57
- this.db
58
- .prepare(`INSERT INTO channels (id, team_id, name, is_channel, is_group, is_im, is_mpim, is_private, is_archived, topic, purpose, creator, created_at, ts_counter)
59
- VALUES (?, ?, ?, ?, ?, 0, 0, ?, 0, ?, ?, ?, ?, 0)`)
60
- .run(channelId, teamId, ch.name, ch.is_private ? 0 : 1, ch.is_private ? 1 : 0, ch.is_private ? 1 : 0, ch.topic ?? "", ch.purpose ?? "", creator, now);
61
- for (const member of ch.members ?? []) {
62
- const memberId = this.resolveSeedUserId(member, userIdByHandle);
63
- if (!memberId)
64
- continue;
65
- this.db
66
- .prepare(`INSERT OR IGNORE INTO channel_members (channel_id, user_id, joined_at) VALUES (?, ?, ?)`)
67
- .run(channelId, memberId, now);
68
- }
69
- // Seeded messages preserve thread relationships.
70
- const seededTsByOriginalTs = new Map();
71
- for (const m of ch.messages ?? []) {
72
- const ts = m.ts ?? this.allocMessageTs(channelId);
73
- if (m.ts)
74
- seededTsByOriginalTs.set(m.ts, ts);
75
- const userId = this.resolveSeedUserId(m.user, userIdByHandle) ?? DEFAULT_BOT_USER;
76
- const threadTs = m.thread_ts ? seededTsByOriginalTs.get(m.thread_ts) ?? m.thread_ts : null;
77
- this.db
78
- .prepare(`INSERT INTO messages (channel_id, ts, user_id, text, subtype, thread_ts, reply_count, reply_users_count, latest_reply, edited_user_id, edited_ts, blocks_json, attachments_json)
79
- VALUES (?, ?, ?, ?, NULL, ?, 0, 0, NULL, NULL, NULL, '[]', '[]')`)
80
- .run(channelId, ts, userId, m.text, threadTs);
81
- if (threadTs) {
82
- this.db
83
- .prepare(`UPDATE messages SET reply_count = reply_count + 1, latest_reply = ?,
84
- reply_users_count = (
85
- SELECT COUNT(DISTINCT user_id) FROM messages WHERE channel_id = ? AND thread_ts = ?
86
- )
87
- WHERE channel_id = ? AND ts = ?`)
88
- .run(ts, channelId, threadTs, channelId, threadTs);
89
- }
90
- for (const r of m.reactions ?? []) {
91
- const reactorId = this.resolveSeedUserId(r.user, userIdByHandle);
92
- if (!reactorId)
93
- continue;
94
- this.db
95
- .prepare(`INSERT OR IGNORE INTO reactions (channel_id, message_ts, name, user_id, added_at) VALUES (?, ?, ?, ?, ?)`)
96
- .run(channelId, ts, r.name, reactorId, now);
97
- }
98
- }
99
- }
100
- })();
101
- }
102
- resetToDefault(seedFn, onDelta = NOOP) {
103
- const beforeSummary = this.summarizeState();
104
- this.seed(seedFn());
105
- const afterSummary = this.summarizeState();
106
- onDelta({ before: beforeSummary, after: afterSummary });
107
- return { ok: true };
108
- }
109
- applySeed(input, onDelta = NOOP) {
110
- const beforeSummary = this.summarizeState();
111
- const next = parseSeed(input);
112
- this.seed(next);
113
- const afterSummary = this.summarizeState();
114
- onDelta({ before: beforeSummary, after: afterSummary });
115
- return { ok: true };
116
- }
117
- exportState() {
118
- const workspace = this.firstWorkspace();
119
- if (!workspace)
120
- return { workspace: null };
121
- return {
122
- workspace: {
123
- id: workspace.id,
124
- name: workspace.name,
125
- domain: workspace.domain,
126
- },
127
- users: this.db.prepare(`SELECT * FROM users WHERE team_id = ? ORDER BY id`).all(workspace.id),
128
- channels: this.allChannels().map((channel) => ({
129
- ...channel,
130
- members: this.db
131
- .prepare(`SELECT user_id FROM channel_members WHERE channel_id = ? ORDER BY user_id`)
132
- .all(channel.id)
133
- .map((row) => row.user_id),
134
- messages: this.db
135
- .prepare(`SELECT * FROM messages WHERE channel_id = ? ORDER BY ts`)
136
- .all(channel.id),
137
- })),
138
- reactions: this.db.prepare(`SELECT * FROM reactions ORDER BY channel_id, message_ts, name, user_id`).all(),
139
- pins: this.db.prepare(`SELECT * FROM pins ORDER BY channel_id, message_ts`).all(),
140
- files: this.db.prepare(`SELECT * FROM files ORDER BY id`).all(),
141
- bookmarks: this.db.prepare(`SELECT * FROM bookmarks ORDER BY id`).all(),
142
- scheduled_messages: this.db.prepare(`SELECT * FROM scheduled_messages ORDER BY id`).all(),
143
- };
144
- }
145
- summarizeState() {
146
- const get = (sql) => this.db.prepare(sql).get().c;
147
- return {
148
- users: get(`SELECT COUNT(*) AS c FROM users`),
149
- channels: get(`SELECT COUNT(*) AS c FROM channels`),
150
- messages: get(`SELECT COUNT(*) AS c FROM messages`),
151
- reactions: get(`SELECT COUNT(*) AS c FROM reactions`),
152
- pins: get(`SELECT COUNT(*) AS c FROM pins`),
153
- files: get(`SELECT COUNT(*) AS c FROM files`),
154
- bookmarks: get(`SELECT COUNT(*) AS c FROM bookmarks`),
155
- scheduled_messages: get(`SELECT COUNT(*) AS c FROM scheduled_messages`),
156
- };
157
- }
158
- // ───────────────────────────────────────────────────────────────────────────
159
- // Auth
160
- // ───────────────────────────────────────────────────────────────────────────
161
- authTest(actor) {
162
- const workspace = this.requireWorkspace();
163
- const userRow = this.resolveActorUser(actor);
164
- return {
165
- url: `${SLACK_TWIN_HOST}/`,
166
- team: workspace.name,
167
- user: userRow.name,
168
- team_id: workspace.id,
169
- user_id: userRow.id,
170
- bot_id: userRow.is_bot ? userRow.id : null,
171
- is_enterprise_install: false,
172
- };
173
- }
174
- // ───────────────────────────────────────────────────────────────────────────
175
- // Conversations
176
- // ───────────────────────────────────────────────────────────────────────────
177
- conversationsList(args) {
178
- const requested = (args.types ?? "public_channel")
179
- .split(",")
180
- .map((t) => t.trim())
181
- .filter(Boolean);
182
- const typeFilters = [];
183
- if (requested.includes("public_channel"))
184
- typeFilters.push(`(is_private = 0 AND is_im = 0 AND is_mpim = 0)`);
185
- if (requested.includes("private_channel"))
186
- typeFilters.push(`(is_private = 1 AND is_im = 0 AND is_mpim = 0)`);
187
- if (requested.includes("mpim"))
188
- typeFilters.push(`is_mpim = 1`);
189
- if (requested.includes("im"))
190
- typeFilters.push(`is_im = 1`);
191
- const where = [];
192
- if (typeFilters.length > 0)
193
- where.push(`(${typeFilters.join(" OR ")})`);
194
- if (args.exclude_archived)
195
- where.push(`is_archived = 0`);
196
- const offset = cursorDecode(args.cursor ?? null)?.offset ?? 0;
197
- const limit = clampLimit(args.limit, 1000, 100);
198
- const stmt = `SELECT * FROM channels${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY created_at, id LIMIT ? OFFSET ?`;
199
- const rows = this.db.prepare(stmt).all(limit + 1, offset);
200
- const hasMore = rows.length > limit;
201
- const slice = rows.slice(0, limit);
202
- const channels = slice.map((row) => {
203
- const numMembers = this.db.prepare(`SELECT COUNT(*) AS c FROM channel_members WHERE channel_id = ?`).get(row.id).c;
204
- return serializeChannel(row, { num_members: numMembers });
205
- });
206
- return {
207
- channels,
208
- response_metadata: {
209
- next_cursor: hasMore ? cursorEncode({ offset: offset + slice.length }) : "",
210
- },
211
- };
212
- }
213
- conversationsInfo(args, actor = {}) {
214
- const channel = this.requireChannel(args.channel);
215
- this.assertChannelMemberForActor(channel, actor);
216
- const numMembers = this.db.prepare(`SELECT COUNT(*) AS c FROM channel_members WHERE channel_id = ?`).get(channel.id).c;
217
- return { channel: serializeChannel(channel, { num_members: numMembers }) };
218
- }
219
- conversationsCreate(args, actor, onDelta = NOOP) {
220
- // Slack distinguishes invalid_name_required (missing), invalid_name_maxlength
221
- // (>80), invalid_name_specials (any letter outside [a-z0-9_-]), and
222
- // invalid_name_punctuation (leading/trailing dash, leading underscore,
223
- // pure digits). Match per https://docs.slack.dev/reference/methods/conversations.create
224
- if (!args.name)
225
- slackError("invalid_name_required", 400);
226
- if (args.name.length > 80)
227
- slackError("invalid_name_maxlength", 400);
228
- if (/[A-Z]/.test(args.name) || /[^a-z0-9_-]/.test(args.name)) {
229
- slackError("invalid_name_specials", 400);
230
- }
231
- if (/^[-_]/.test(args.name) || /^\d+$/.test(args.name)) {
232
- slackError("invalid_name_punctuation", 400);
233
- }
234
- const workspace = this.requireWorkspace();
235
- const creator = this.resolveActorUser(actor).id;
236
- const channelId = this.allocId(workspace.id, args.is_private ? "G" : "C");
237
- const now = nowIso();
238
- const out = this.db.transaction(() => {
239
- const existing = this.db
240
- .prepare(`SELECT id FROM channels WHERE team_id = ? AND name = ?`)
241
- .get(workspace.id, args.name);
242
- if (existing)
243
- slackError("name_taken", 409);
244
- this.db
245
- .prepare(`INSERT INTO channels (id, team_id, name, is_channel, is_group, is_im, is_mpim, is_private, is_archived, topic, purpose, creator, created_at, ts_counter)
246
- VALUES (?, ?, ?, ?, ?, 0, 0, ?, 0, '', '', ?, ?, 0)`)
247
- .run(channelId, workspace.id, args.name, args.is_private ? 0 : 1, args.is_private ? 1 : 0, args.is_private ? 1 : 0, creator, now);
248
- this.db
249
- .prepare(`INSERT OR IGNORE INTO channel_members (channel_id, user_id, joined_at) VALUES (?, ?, ?)`)
250
- .run(channelId, creator, now);
251
- const row = this.requireChannelRow(channelId);
252
- onDelta({ before: null, after: row });
253
- return serializeChannel(row, { num_members: 1, is_member: true });
254
- })();
255
- return { channel: out };
256
- }
257
- conversationsArchive(args, actor, onDelta = NOOP) {
258
- const channel = this.requireChannel(args.channel);
259
- if (channel.is_archived)
260
- slackError("already_archived", 400);
261
- if (channel.name === "general")
262
- slackError("cant_archive_general", 400);
263
- const acting = this.resolveActorUser(actor);
264
- if (!acting.is_admin)
265
- this.assertChannelMember(channel, acting.id);
266
- const out = this.db.transaction(() => {
267
- const before = this.requireChannelRow(channel.id);
268
- this.db.prepare(`UPDATE channels SET is_archived = 1 WHERE id = ?`).run(channel.id);
269
- const after = this.requireChannelRow(channel.id);
270
- onDelta({ before, after });
271
- return { ok: true };
272
- })();
273
- return out;
274
- }
275
- conversationsInvite(args, actor, onDelta = NOOP) {
276
- const channel = this.requireChannel(args.channel);
277
- if (channel.is_archived)
278
- slackError("is_archived", 400);
279
- const acting = this.resolveActorUser(actor);
280
- if (!acting.is_admin)
281
- this.assertChannelMember(channel, acting.id);
282
- const userIds = (args.users ?? "")
283
- .split(",")
284
- .map((s) => s.trim())
285
- .filter(Boolean);
286
- if (userIds.length === 0)
287
- slackError("no_user", 400);
288
- const now = nowIso();
289
- const out = this.db.transaction(() => {
290
- const beforeMembers = this.channelMemberIds(channel.id);
291
- for (const userRef of userIds) {
292
- const user = this.resolveUser(userRef);
293
- if (!user)
294
- slackError("user_not_found", 404);
295
- const exists = this.db
296
- .prepare(`SELECT 1 FROM channel_members WHERE channel_id = ? AND user_id = ?`)
297
- .get(channel.id, user.id);
298
- if (exists)
299
- slackError("already_in_channel", 400);
300
- this.db
301
- .prepare(`INSERT INTO channel_members (channel_id, user_id, joined_at) VALUES (?, ?, ?)`)
302
- .run(channel.id, user.id, now);
303
- }
304
- const afterMembers = this.channelMemberIds(channel.id);
305
- onDelta({
306
- before: { channel_id: channel.id, members: beforeMembers },
307
- after: { channel_id: channel.id, members: afterMembers },
308
- });
309
- return { channel: serializeChannel(channel, { num_members: afterMembers.length }) };
310
- })();
311
- return out;
312
- }
313
- conversationsJoin(args, actor, onDelta = NOOP) {
314
- const channel = this.requireChannel(args.channel);
315
- if (channel.is_archived)
316
- slackError("is_archived", 400);
317
- if (channel.is_private)
318
- slackError("channel_not_found", 404);
319
- const userId = this.resolveActorUser(actor).id;
320
- const now = nowIso();
321
- const out = this.db.transaction(() => {
322
- const existing = this.db
323
- .prepare(`SELECT * FROM channel_members WHERE channel_id = ? AND user_id = ?`)
324
- .get(channel.id, userId);
325
- if (existing) {
326
- return { channel: serializeChannel(channel, { is_member: true }), already_in_channel: true, warning: "already_in_channel" };
327
- }
328
- this.db
329
- .prepare(`INSERT INTO channel_members (channel_id, user_id, joined_at) VALUES (?, ?, ?)`)
330
- .run(channel.id, userId, now);
331
- const after = this.db
332
- .prepare(`SELECT * FROM channel_members WHERE channel_id = ? AND user_id = ?`)
333
- .get(channel.id, userId);
334
- onDelta({ before: null, after });
335
- return { channel: serializeChannel(channel, { is_member: true }) };
336
- })();
337
- return out;
338
- }
339
- conversationsLeave(args, actor, onDelta = NOOP) {
340
- const channel = this.requireChannel(args.channel);
341
- if (channel.name === "general")
342
- slackError("cant_leave_general", 400);
343
- const userId = this.resolveActorUser(actor).id;
344
- const out = this.db.transaction(() => {
345
- const existing = this.db
346
- .prepare(`SELECT * FROM channel_members WHERE channel_id = ? AND user_id = ?`)
347
- .get(channel.id, userId);
348
- if (!existing)
349
- slackError("not_in_channel", 400);
350
- this.db.prepare(`DELETE FROM channel_members WHERE channel_id = ? AND user_id = ?`).run(channel.id, userId);
351
- onDelta({ before: existing, after: null });
352
- return { not_in_channel: false };
353
- })();
354
- return out;
355
- }
356
- conversationsKick(args, actor, onDelta = NOOP) {
357
- const channel = this.requireChannel(args.channel);
358
- if (channel.name === "general")
359
- slackError("cant_kick_from_general", 400);
360
- const acting = this.resolveActorUser(actor);
361
- const target = this.resolveUser(args.user);
362
- if (!target)
363
- slackError("user_not_found", 404);
364
- if (target.id === acting.id)
365
- slackError("cant_kick_self", 400);
366
- const out = this.db.transaction(() => {
367
- const existing = this.db
368
- .prepare(`SELECT * FROM channel_members WHERE channel_id = ? AND user_id = ?`)
369
- .get(channel.id, target.id);
370
- if (!existing)
371
- slackError("not_in_channel", 400);
372
- this.db.prepare(`DELETE FROM channel_members WHERE channel_id = ? AND user_id = ?`).run(channel.id, target.id);
373
- onDelta({ before: existing, after: null });
374
- return {};
375
- })();
376
- return out;
377
- }
378
- conversationsMembers(args, actor = {}) {
379
- const channel = this.requireChannel(args.channel);
380
- this.assertChannelMemberForActor(channel, actor);
381
- const offset = cursorDecode(args.cursor ?? null)?.offset ?? 0;
382
- const limit = clampLimit(args.limit, 1000, 100);
383
- const rows = this.db
384
- .prepare(`SELECT user_id FROM channel_members WHERE channel_id = ? ORDER BY joined_at, user_id LIMIT ? OFFSET ?`)
385
- .all(channel.id, limit + 1, offset);
386
- const hasMore = rows.length > limit;
387
- const slice = rows.slice(0, limit);
388
- return {
389
- members: slice.map((row) => row.user_id),
390
- response_metadata: {
391
- next_cursor: hasMore ? cursorEncode({ offset: offset + slice.length }) : "",
392
- },
393
- };
394
- }
395
- conversationsHistory(args, actor = {}) {
396
- const channel = this.requireChannel(args.channel);
397
- this.assertChannelMemberForActor(channel, actor);
398
- const offset = cursorDecode(args.cursor ?? null)?.offset ?? 0;
399
- const limit = clampLimit(args.limit, 1000, 100);
400
- const params = [channel.id];
401
- const where = [`channel_id = ?`, `(thread_ts IS NULL OR ts = thread_ts)`];
402
- if (args.oldest) {
403
- where.push(args.inclusive ? `ts >= ?` : `ts > ?`);
404
- params.push(args.oldest);
405
- }
406
- if (args.latest) {
407
- where.push(args.inclusive ? `ts <= ?` : `ts < ?`);
408
- params.push(args.latest);
409
- }
410
- const sql = `SELECT * FROM messages WHERE ${where.join(" AND ")} ORDER BY ts DESC LIMIT ? OFFSET ?`;
411
- params.push(limit + 1, offset);
412
- const rows = this.db.prepare(sql).all(...params);
413
- const hasMore = rows.length > limit;
414
- const slice = rows.slice(0, limit);
415
- const workspaceId = this.requireWorkspace().id;
416
- return {
417
- messages: slice.map((row) => serializeMessage(row, { reactions: this.reactionsFor(row.channel_id, row.ts), include_team: workspaceId })),
418
- has_more: hasMore,
419
- pin_count: this.db.prepare(`SELECT COUNT(*) AS c FROM pins WHERE channel_id = ?`).get(channel.id).c,
420
- channel_actions_ts: null,
421
- channel_actions_count: 0,
422
- // Real Slack OMITS `response_metadata` on conversations.history when there is
423
- // no next page (it only carries the pagination cursor when hasMore); it does
424
- // NOT return an empty-cursor envelope here the way conversations.list does
425
- // (FDRS-473 Kind B — conditional pagination matched per endpoint).
426
- ...(hasMore
427
- ? { response_metadata: { next_cursor: cursorEncode({ offset: offset + slice.length }) } }
428
- : {}),
429
- };
430
- }
431
- conversationsReplies(args, actor = {}) {
432
- if (!args.ts)
433
- slackError("invalid_arguments", 400);
434
- const channel = this.requireChannel(args.channel);
435
- this.assertChannelMemberForActor(channel, actor);
436
- const parent = this.db
437
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
438
- .get(channel.id, args.ts);
439
- if (!parent)
440
- slackError("thread_not_found", 404);
441
- const offset = cursorDecode(args.cursor ?? null)?.offset ?? 0;
442
- const limit = clampLimit(args.limit, 1000, 100);
443
- const rows = this.db
444
- .prepare(`SELECT * FROM messages
445
- WHERE channel_id = ? AND (ts = ? OR thread_ts = ?)
446
- ORDER BY ts ASC LIMIT ? OFFSET ?`)
447
- .all(channel.id, args.ts, args.ts, limit + 1, offset);
448
- const hasMore = rows.length > limit;
449
- const slice = rows.slice(0, limit);
450
- const workspaceId = this.requireWorkspace().id;
451
- const parentAuthor = parent.user_id;
452
- const parentTs = parent.ts;
453
- return {
454
- messages: slice.map((row) => {
455
- const isParent = row.ts === parentTs && !row.thread_ts;
456
- const serialized = serializeMessage(row, {
457
- reactions: this.reactionsFor(row.channel_id, row.ts),
458
- include_team: workspaceId,
459
- parent_user_id: row.thread_ts ? parentAuthor : undefined,
460
- });
461
- if (isParent) {
462
- // Real Slack conversations.replies decorates the thread parent
463
- // with thread_ts === ts (even though the stored row has no
464
- // thread_ts), plus subscribed/is_locked. Every thread-walking
465
- // SDK consumer relies on this invariant.
466
- serialized.thread_ts = parentTs;
467
- serialized.subscribed = false;
468
- serialized.is_locked = false;
469
- }
470
- return serialized;
471
- }),
472
- has_more: hasMore,
473
- // Real Slack OMITS `response_metadata` on conversations.replies when there is
474
- // no next page (cursor present only when hasMore) — FDRS-473 Kind B.
475
- ...(hasMore
476
- ? { response_metadata: { next_cursor: cursorEncode({ offset: offset + slice.length }) } }
477
- : {}),
478
- };
479
- }
480
- conversationsOpen(args, actor, onDelta = NOOP) {
481
- const workspace = this.requireWorkspace();
482
- const acting = this.resolveActorUser(actor);
483
- if (args.channel) {
484
- // Re-open existing DM/MPIM.
485
- const existing = this.requireChannel(args.channel);
486
- onDelta({ before: existing, after: existing });
487
- return { no_op: true, already_open: true, channel: serializeChannel(existing, { is_member: true }) };
488
- }
489
- if (!args.users)
490
- slackError("users_list_not_supplied", 400);
491
- const targetIds = args.users
492
- .split(",")
493
- .map((s) => s.trim())
494
- .filter(Boolean)
495
- .map((u) => {
496
- const found = this.resolveUser(u);
497
- if (!found)
498
- slackError("user_not_found", 404);
499
- return found.id;
500
- });
501
- const allIds = [...new Set([acting.id, ...targetIds])].sort();
502
- const isMpim = allIds.length > 2;
503
- const lookupName = isMpim ? `mpdm-${allIds.join("--")}` : "";
504
- const dmSignature = allIds.join("|");
505
- const out = this.db.transaction(() => {
506
- const existingChannel = this.findDirectChannelBySignature(workspace.id, dmSignature);
507
- if (existingChannel) {
508
- onDelta({ before: existingChannel, after: existingChannel });
509
- return { no_op: true, already_open: true, channel: serializeChannel(existingChannel, { is_member: true }) };
510
- }
511
- const channelId = this.allocId(workspace.id, isMpim ? "M" : "D");
512
- const now = nowIso();
513
- this.db
514
- .prepare(`INSERT INTO channels (id, team_id, name, is_channel, is_group, is_im, is_mpim, is_private, is_archived, topic, purpose, creator, created_at, ts_counter, dm_signature)
515
- VALUES (?, ?, ?, 0, 0, ?, ?, 1, 0, '', '', ?, ?, 0, ?)`)
516
- .run(channelId, workspace.id, lookupName, isMpim ? 0 : 1, isMpim ? 1 : 0, acting.id, now, dmSignature);
517
- for (const id of allIds) {
518
- this.db
519
- .prepare(`INSERT OR IGNORE INTO channel_members (channel_id, user_id, joined_at) VALUES (?, ?, ?)`)
520
- .run(channelId, id, now);
521
- }
522
- const created = this.requireChannelRow(channelId);
523
- onDelta({ before: null, after: created });
524
- return { channel: serializeChannel(created, { is_member: true, num_members: allIds.length }) };
525
- })();
526
- return out;
527
- }
528
- // ───────────────────────────────────────────────────────────────────────────
529
- // Chat
530
- // ───────────────────────────────────────────────────────────────────────────
531
- chatPostMessage(args, actor, onDelta = NOOP) {
532
- const channel = this.requireChannel(args.channel);
533
- if (channel.is_archived)
534
- slackError("is_archived", 400);
535
- if (!args.text && !args.blocks && !args.attachments)
536
- slackError("no_text", 400);
537
- if (args.thread_ts) {
538
- const parent = this.db
539
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
540
- .get(channel.id, args.thread_ts);
541
- if (!parent)
542
- slackError("thread_not_found", 404);
543
- }
544
- const author = this.resolveActorUser(actor);
545
- if (channel.is_private || channel.is_im || channel.is_mpim) {
546
- const member = this.db
547
- .prepare(`SELECT 1 FROM channel_members WHERE channel_id = ? AND user_id = ?`)
548
- .get(channel.id, author.id);
549
- if (!member)
550
- slackError("not_in_channel", 400);
551
- }
552
- const ts = this.allocMessageTs(channel.id);
553
- const blocks = args.blocks ? sanitizeJsonString(args.blocks, "[]") : "[]";
554
- const attachments = args.attachments ? sanitizeJsonString(args.attachments, "[]") : "[]";
555
- const subtype = author.is_bot ? "bot_message" : null;
556
- // For bot authors: bot_id = the bot user's id; app_id = synthetic A_POME so
557
- // SDK consumers see a non-null app_id consistent with real Slack.
558
- const botId = author.is_bot ? author.id : null;
559
- const appId = author.is_bot ? "A_POME" : null;
560
- const username = typeof args.username === "string" && args.username.length > 0 ? args.username : null;
561
- const iconUrl = typeof args.icon_url === "string" && args.icon_url.length > 0 ? args.icon_url : null;
562
- const iconEmoji = typeof args.icon_emoji === "string" && args.icon_emoji.length > 0 ? args.icon_emoji : null;
563
- const out = this.db.transaction(() => {
564
- this.db
565
- .prepare(`INSERT INTO messages (channel_id, ts, user_id, text, subtype, thread_ts, blocks_json, attachments_json,
566
- bot_id, app_id, username, icon_url, icon_emoji)
567
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
568
- .run(channel.id, ts, author.id, args.text ?? "", subtype, args.thread_ts ?? null, blocks, attachments, botId, appId, username, iconUrl, iconEmoji);
569
- if (args.thread_ts) {
570
- this.db
571
- .prepare(`UPDATE messages SET reply_count = reply_count + 1, latest_reply = ?,
572
- reply_users_count = (
573
- SELECT COUNT(DISTINCT user_id) FROM messages WHERE channel_id = ? AND thread_ts = ?
574
- )
575
- WHERE channel_id = ? AND ts = ?`)
576
- .run(ts, channel.id, args.thread_ts, channel.id, args.thread_ts);
577
- }
578
- const row = this.requireMessageRow(channel.id, ts);
579
- onDelta({ before: null, after: row });
580
- const workspaceId = this.requireWorkspace().id;
581
- return { channel: channel.id, ts, message: serializeMessage(row, { include_team: workspaceId }) };
582
- })();
583
- return out;
584
- }
585
- chatUpdate(args, actor, onDelta = NOOP) {
586
- const channel = this.requireChannel(args.channel);
587
- if (channel.is_archived)
588
- slackError("is_archived", 400);
589
- const acting = this.resolveActorUser(actor);
590
- const before = this.db
591
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
592
- .get(channel.id, args.ts);
593
- if (!before)
594
- slackError("message_not_found", 404);
595
- // Real Slack has no admin override at the API layer — only the message's
596
- // author may update their own message. Single-tenant sandbox semantics:
597
- // the JWT's `login` claim identifies the acting user.
598
- if (before.user_id !== acting.id)
599
- slackError("cant_update_message", 403);
600
- const out = this.db.transaction(() => {
601
- const updatedTs = this.allocMessageTs(channel.id);
602
- const newText = args.text ?? before.text;
603
- const newBlocks = args.blocks ? sanitizeJsonString(args.blocks, before.blocks_json) : before.blocks_json;
604
- const newAttachments = args.attachments
605
- ? sanitizeJsonString(args.attachments, before.attachments_json)
606
- : before.attachments_json;
607
- this.db
608
- .prepare(`UPDATE messages SET text = ?, blocks_json = ?, attachments_json = ?, edited_user_id = ?, edited_ts = ? WHERE channel_id = ? AND ts = ?`)
609
- .run(newText, newBlocks, newAttachments, acting.id, updatedTs, channel.id, args.ts);
610
- const after = this.requireMessageRow(channel.id, args.ts);
611
- onDelta({ before, after });
612
- return { channel: channel.id, ts: args.ts, text: after.text, message: serializeMessage(after) };
613
- })();
614
- return out;
615
- }
616
- chatDelete(args, actor, onDelta = NOOP) {
617
- const channel = this.requireChannel(args.channel);
618
- const acting = this.resolveActorUser(actor);
619
- const before = this.db
620
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
621
- .get(channel.id, args.ts);
622
- if (!before)
623
- slackError("message_not_found", 404);
624
- // Real Slack has no admin override on chat.delete — only the author may delete.
625
- if (before.user_id !== acting.id)
626
- slackError("cant_delete_message", 403);
627
- const out = this.db.transaction(() => {
628
- this.db.prepare(`DELETE FROM messages WHERE channel_id = ? AND ts = ?`).run(channel.id, args.ts);
629
- // Decrement thread parent if this was a reply.
630
- if (before.thread_ts) {
631
- this.db
632
- .prepare(`UPDATE messages SET reply_count = MAX(reply_count - 1, 0),
633
- reply_users_count = (
634
- SELECT COUNT(DISTINCT user_id) FROM messages WHERE channel_id = ? AND thread_ts = ?
635
- ),
636
- latest_reply = (
637
- SELECT ts FROM messages WHERE channel_id = ? AND thread_ts = ? ORDER BY ts DESC LIMIT 1
638
- )
639
- WHERE channel_id = ? AND ts = ?`)
640
- .run(channel.id, before.thread_ts, channel.id, before.thread_ts, channel.id, before.thread_ts);
641
- }
642
- onDelta({ before, after: null });
643
- return { channel: channel.id, ts: args.ts };
644
- })();
645
- return out;
646
- }
647
- chatScheduleMessage(args, actor, onDelta = NOOP) {
648
- const channel = this.requireChannel(args.channel);
649
- if (channel.is_archived)
650
- slackError("is_archived", 400);
651
- if (!args.text)
652
- slackError("no_text", 400);
653
- if (!args.post_at || args.post_at <= nowUnix())
654
- slackError("time_in_past", 400);
655
- const acting = this.resolveActorUser(actor);
656
- const id = this.allocId(this.requireWorkspace().id, "Q");
657
- const now = nowUnix();
658
- const out = this.db.transaction(() => {
659
- this.db
660
- .prepare(`INSERT INTO scheduled_messages (id, channel_id, user_id, text, thread_ts, post_at, date_created, blocks_json)
661
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
662
- .run(id, channel.id, acting.id, args.text, args.thread_ts ?? null, args.post_at, now, args.blocks ? sanitizeJsonString(args.blocks, "[]") : "[]");
663
- const row = this.requireScheduledMessage(id);
664
- onDelta({ before: null, after: row });
665
- return {
666
- channel: channel.id,
667
- scheduled_message_id: id,
668
- post_at: args.post_at,
669
- message: { text: args.text, type: "delayed_message" },
670
- };
671
- })();
672
- return out;
673
- }
674
- chatDeleteScheduledMessage(args, actor, onDelta = NOOP) {
675
- void actor;
676
- const channel = this.requireChannel(args.channel);
677
- const row = this.db
678
- .prepare(`SELECT * FROM scheduled_messages WHERE id = ? AND channel_id = ?`)
679
- .get(args.scheduled_message_id, channel.id);
680
- if (!row)
681
- slackError("scheduled_message_not_found", 404);
682
- const out = this.db.transaction(() => {
683
- this.db.prepare(`DELETE FROM scheduled_messages WHERE id = ?`).run(row.id);
684
- onDelta({ before: row, after: null });
685
- return {};
686
- })();
687
- return out;
688
- }
689
- // ───────────────────────────────────────────────────────────────────────────
690
- // Reactions
691
- // ───────────────────────────────────────────────────────────────────────────
692
- reactionsAdd(args, actor, onDelta = NOOP) {
693
- if (!args.name)
694
- slackError("no_reaction", 400);
695
- const channel = this.requireChannel(args.channel);
696
- const message = this.db
697
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
698
- .get(channel.id, args.timestamp);
699
- if (!message)
700
- slackError("message_not_found", 404);
701
- const acting = this.resolveActorUser(actor);
702
- const reaction = normalizeReactionName(args.name);
703
- const out = this.db.transaction(() => {
704
- const existing = this.db
705
- .prepare(`SELECT * FROM reactions WHERE channel_id = ? AND message_ts = ? AND name = ? AND user_id = ?`)
706
- .get(channel.id, args.timestamp, reaction, acting.id);
707
- if (existing)
708
- slackError("already_reacted", 400);
709
- const now = nowIso();
710
- this.db
711
- .prepare(`INSERT INTO reactions (channel_id, message_ts, name, user_id, added_at) VALUES (?, ?, ?, ?, ?)`)
712
- .run(channel.id, args.timestamp, reaction, acting.id, now);
713
- const after = this.db
714
- .prepare(`SELECT * FROM reactions WHERE channel_id = ? AND message_ts = ? AND name = ? AND user_id = ?`)
715
- .get(channel.id, args.timestamp, reaction, acting.id);
716
- onDelta({ before: null, after });
717
- return {};
718
- })();
719
- return out;
720
- }
721
- reactionsRemove(args, actor, onDelta = NOOP) {
722
- if (!args.name)
723
- slackError("no_reaction", 400);
724
- const channel = this.requireChannel(args.channel);
725
- const acting = this.resolveActorUser(actor);
726
- const reaction = normalizeReactionName(args.name);
727
- const before = this.db
728
- .prepare(`SELECT * FROM reactions WHERE channel_id = ? AND message_ts = ? AND name = ? AND user_id = ?`)
729
- .get(channel.id, args.timestamp, reaction, acting.id);
730
- if (!before)
731
- slackError("no_reaction", 404);
732
- const out = this.db.transaction(() => {
733
- this.db
734
- .prepare(`DELETE FROM reactions WHERE channel_id = ? AND message_ts = ? AND name = ? AND user_id = ?`)
735
- .run(channel.id, args.timestamp, reaction, acting.id);
736
- onDelta({ before, after: null });
737
- return {};
738
- })();
739
- return out;
740
- }
741
- reactionsGet(args, actor = {}) {
742
- const channel = this.requireChannel(args.channel);
743
- this.assertChannelMemberForActor(channel, actor);
744
- const message = this.db
745
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
746
- .get(channel.id, args.timestamp);
747
- if (!message)
748
- slackError("message_not_found", 404);
749
- const reactions = this.reactionsFor(channel.id, args.timestamp);
750
- return {
751
- type: "message",
752
- channel: channel.id,
753
- message: { ...serializeMessage(message, { reactions }) },
754
- };
755
- }
756
- // ───────────────────────────────────────────────────────────────────────────
757
- // Users
758
- // ───────────────────────────────────────────────────────────────────────────
759
- usersList(args) {
760
- const workspace = this.requireWorkspace();
761
- const offset = cursorDecode(args.cursor ?? null)?.offset ?? 0;
762
- const limit = clampLimit(args.limit, 1000, 100);
763
- const rows = this.db
764
- .prepare(`SELECT * FROM users WHERE team_id = ? ORDER BY id LIMIT ? OFFSET ?`)
765
- .all(workspace.id, limit + 1, offset);
766
- const hasMore = rows.length > limit;
767
- const slice = rows.slice(0, limit);
768
- return {
769
- // users.list embeds each user's profile WITHOUT `email` — real Slack only
770
- // returns the email on the single-user reads (FDRS-473 Kind B).
771
- members: slice.map((row) => serializeUser(row, { include_locale: args.include_locale, omitProfileEmail: true })),
772
- cache_ts: nowUnix(),
773
- response_metadata: {
774
- next_cursor: hasMore ? cursorEncode({ offset: offset + slice.length }) : "",
775
- },
776
- };
777
- }
778
- usersInfo(args) {
779
- const user = this.resolveUser(args.user);
780
- if (!user)
781
- slackError("user_not_found", 404);
782
- return { user: serializeUser(user, { include_locale: args.include_locale }) };
783
- }
784
- usersLookupByEmail(args) {
785
- if (!args.email)
786
- slackError("users_not_found", 400);
787
- const workspace = this.requireWorkspace();
788
- const user = this.db
789
- .prepare(`SELECT * FROM users WHERE team_id = ? AND email = ?`)
790
- .get(workspace.id, args.email);
791
- if (!user)
792
- slackError("users_not_found", 404);
793
- return { user: serializeUser(user) };
794
- }
795
- usersProfileGet(args, actor) {
796
- const target = args.user ? this.resolveUser(args.user) : this.resolveActorUser(actor);
797
- if (!target)
798
- slackError("user_not_found", 404);
799
- // users.profile.get returns the bare profile WITHOUT `team` — real Slack omits
800
- // it on this endpoint (it carries it in the embedded user.profile elsewhere)
801
- // (FDRS-473 Kind B).
802
- return { profile: serializeUserProfile(target, { omitTeam: true }) };
803
- }
804
- usersProfileSet(args, actor, onDelta = NOOP) {
805
- const target = args.user ? this.resolveUser(args.user) : this.resolveActorUser(actor);
806
- if (!target)
807
- slackError("user_not_found", 404);
808
- const acting = this.resolveActorUser(actor);
809
- if (target.id !== acting.id && !acting.is_admin)
810
- slackError("cant_set_profile_for_other_user", 403);
811
- const out = this.db.transaction(() => {
812
- const before = this.db.prepare(`SELECT * FROM users WHERE id = ?`).get(target.id);
813
- let profile = safeParseJson(before.profile_json);
814
- if (args.profile) {
815
- const incoming = safeParseJson(args.profile);
816
- profile = { ...profile, ...incoming };
817
- }
818
- else if (args.name) {
819
- profile = { ...profile, [args.name]: args.value ?? "" };
820
- }
821
- this.db
822
- .prepare(`UPDATE users SET profile_json = ?, real_name = COALESCE((SELECT json_extract(?, '$.real_name')), real_name), updated_at = ? WHERE id = ?`)
823
- .run(JSON.stringify(profile), JSON.stringify(profile), nowIso(), target.id);
824
- const after = this.db.prepare(`SELECT * FROM users WHERE id = ?`).get(target.id);
825
- onDelta({ before, after });
826
- // users.profile.set echoes the bare profile, same shape as users.profile.get
827
- // (no `team`) — FDRS-473 Kind B.
828
- return { profile: serializeUserProfile(after, { omitTeam: true }) };
829
- })();
830
- return out;
831
- }
832
- // ───────────────────────────────────────────────────────────────────────────
833
- // Pins
834
- // ───────────────────────────────────────────────────────────────────────────
835
- pinsAdd(args, actor, onDelta = NOOP) {
836
- const channel = this.requireChannel(args.channel);
837
- const message = this.db
838
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
839
- .get(channel.id, args.timestamp);
840
- if (!message)
841
- slackError("message_not_found", 404);
842
- const acting = this.resolveActorUser(actor);
843
- const out = this.db.transaction(() => {
844
- const existing = this.db
845
- .prepare(`SELECT * FROM pins WHERE channel_id = ? AND message_ts = ?`)
846
- .get(channel.id, args.timestamp);
847
- if (existing)
848
- slackError("already_pinned", 400);
849
- this.db
850
- .prepare(`INSERT INTO pins (channel_id, message_ts, pinned_by, pinned_at) VALUES (?, ?, ?, ?)`)
851
- .run(channel.id, args.timestamp, acting.id, nowIso());
852
- const after = this.db
853
- .prepare(`SELECT * FROM pins WHERE channel_id = ? AND message_ts = ?`)
854
- .get(channel.id, args.timestamp);
855
- onDelta({ before: null, after });
856
- return {};
857
- })();
858
- return out;
859
- }
860
- pinsRemove(args, actor, onDelta = NOOP) {
861
- void actor;
862
- const channel = this.requireChannel(args.channel);
863
- const before = this.db
864
- .prepare(`SELECT * FROM pins WHERE channel_id = ? AND message_ts = ?`)
865
- .get(channel.id, args.timestamp);
866
- if (!before)
867
- slackError("no_pin", 404);
868
- const out = this.db.transaction(() => {
869
- this.db.prepare(`DELETE FROM pins WHERE channel_id = ? AND message_ts = ?`).run(channel.id, args.timestamp);
870
- onDelta({ before, after: null });
871
- return {};
872
- })();
873
- return out;
874
- }
875
- pinsList(args, actor = {}) {
876
- const channel = this.requireChannel(args.channel);
877
- this.assertChannelMemberForActor(channel, actor);
878
- const pins = this.db
879
- .prepare(`SELECT * FROM pins WHERE channel_id = ? ORDER BY pinned_at`)
880
- .all(channel.id);
881
- const items = pins.map((pin) => {
882
- const message = this.db
883
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
884
- .get(pin.channel_id, pin.message_ts);
885
- const serializedMessage = message ? serializeMessage(message, { reactions: this.reactionsFor(pin.channel_id, pin.message_ts) }) : { ts: pin.message_ts };
886
- return serializePin(pin, serializedMessage);
887
- });
888
- return { items };
889
- }
890
- // ───────────────────────────────────────────────────────────────────────────
891
- // Search
892
- // ───────────────────────────────────────────────────────────────────────────
893
- searchMessages(args, actor = {}) {
894
- if (!args.query || args.query.trim() === "")
895
- slackError("no_query", 400);
896
- const acting = this.resolveActorUser(actor);
897
- const count = clampLimit(args.count, 100, 20);
898
- const page = Math.max(1, Math.floor(args.page ?? 1));
899
- const offset = (page - 1) * count;
900
- const sortDir = (args.sort_dir ?? "desc").toLowerCase() === "asc" ? "ASC" : "DESC";
901
- // Simple LIKE-based search. Slack's actual full-text engine is more
902
- // sophisticated; for twin scenarios this is enough to match seeded
903
- // and posted messages on substring.
904
- const like = `%${args.query.replace(/[%_]/g, (m) => `\\${m}`)}%`;
905
- const visibilityClause = `(
906
- channel_id IN (SELECT id FROM channels WHERE is_private = 0 AND is_im = 0 AND is_mpim = 0)
907
- OR channel_id IN (SELECT channel_id FROM channel_members WHERE user_id = ?)
908
- )`;
909
- const total = this.db
910
- .prepare(`SELECT COUNT(*) AS c FROM messages WHERE text LIKE ? ESCAPE '\\' AND ${visibilityClause}`)
911
- .get(like, acting.id).c;
912
- const rows = this.db
913
- .prepare(`SELECT * FROM messages
914
- WHERE text LIKE ? ESCAPE '\\' AND ${visibilityClause}
915
- ORDER BY ts ${sortDir}
916
- LIMIT ? OFFSET ?`)
917
- .all(like, acting.id, count, offset);
918
- const matches = rows.map((row) => {
919
- const channel = this.db.prepare(`SELECT * FROM channels WHERE id = ?`).get(row.channel_id);
920
- return {
921
- iid: `${row.channel_id}-${row.ts}`,
922
- type: "message",
923
- user: row.user_id,
924
- username: this.userNameOf(row.user_id),
925
- ts: row.ts,
926
- text: row.text,
927
- permalink: `${SLACK_TWIN_HOST}/archives/${row.channel_id}/p${row.ts.replace(".", "")}`,
928
- team: this.requireWorkspace().id,
929
- channel: channel ? { id: channel.id, name: channel.name, is_private: Boolean(channel.is_private) } : { id: row.channel_id, name: "", is_private: false },
930
- };
931
- });
932
- return {
933
- query: args.query,
934
- messages: {
935
- total,
936
- pagination: {
937
- page,
938
- page_count: total === 0 ? 0 : Math.ceil(total / count),
939
- per_page: count,
940
- total_count: total,
941
- first: offset + 1,
942
- last: offset + matches.length,
943
- },
944
- paging: {
945
- count,
946
- total,
947
- page,
948
- pages: total === 0 ? 0 : Math.ceil(total / count),
949
- },
950
- matches,
951
- },
952
- };
953
- }
954
- // ───────────────────────────────────────────────────────────────────────────
955
- // Files
956
- // ───────────────────────────────────────────────────────────────────────────
957
- filesUpload(args, actor, onDelta = NOOP) {
958
- const workspace = this.requireWorkspace();
959
- const acting = this.resolveActorUser(actor);
960
- const channelsRaw = args.channels ?? args.channel ?? "";
961
- const channelIds = channelsRaw
962
- .split(",")
963
- .map((s) => s.trim())
964
- .filter(Boolean)
965
- .map((ref) => this.requireChannel(ref).id);
966
- const id = this.allocId(workspace.id, "F");
967
- const name = args.filename ?? `untitled-${id}.txt`;
968
- const title = args.title ?? name;
969
- const filetype = args.filetype ?? "text";
970
- const content = args.content ?? null;
971
- const size = content ? Buffer.byteLength(content, "utf8") : 0;
972
- const out = this.db.transaction(() => {
973
- this.db
974
- .prepare(`INSERT INTO files (id, team_id, user_id, name, title, mimetype, filetype, size, url_private, channels_json, deleted, content, created_at)
975
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, '', ?, 0, ?, ?)`)
976
- .run(id, workspace.id, acting.id, name, title, filetypeMimetype(filetype), filetype, size, JSON.stringify(channelIds), content, nowIso());
977
- const after = this.db.prepare(`SELECT * FROM files WHERE id = ?`).get(id);
978
- // Optionally post initial_comment as a message in the first channel.
979
- if (args.initial_comment && channelIds.length > 0) {
980
- const ts = this.allocMessageTs(channelIds[0]);
981
- this.db
982
- .prepare(`INSERT INTO messages (channel_id, ts, user_id, text, subtype, thread_ts, blocks_json, attachments_json)
983
- VALUES (?, ?, ?, ?, ?, ?, '[]', ?)`)
984
- .run(channelIds[0], ts, acting.id, args.initial_comment, "file_share", args.thread_ts ?? null, JSON.stringify([{ id: 1, file_id: id, title }]));
985
- }
986
- onDelta({ before: null, after });
987
- return { file: serializeFile(after) };
988
- })();
989
- return out;
990
- }
991
- filesInfo(args) {
992
- const file = this.db.prepare(`SELECT * FROM files WHERE id = ?`).get(args.file);
993
- if (!file)
994
- slackError("file_not_found", 404);
995
- return {
996
- file: serializeFile(file),
997
- comments: [],
998
- response_metadata: { next_cursor: "" },
999
- };
1000
- }
1001
- filesList(args) {
1002
- const count = clampLimit(args.count, 1000, 100);
1003
- const page = Math.max(1, Math.floor(args.page ?? 1));
1004
- const offset = (page - 1) * count;
1005
- const params = [];
1006
- const where = [`deleted = 0`];
1007
- if (args.user) {
1008
- const user = this.resolveUser(args.user);
1009
- if (!user)
1010
- slackError("user_not_found", 404);
1011
- where.push(`user_id = ?`);
1012
- params.push(user.id);
1013
- }
1014
- let sql = `SELECT * FROM files WHERE ${where.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`;
1015
- params.push(count, offset);
1016
- let rows = this.db.prepare(sql).all(...params);
1017
- if (args.channel) {
1018
- const channelId = this.requireChannel(args.channel).id;
1019
- rows = rows.filter((row) => JSON.parse(row.channels_json).includes(channelId));
1020
- }
1021
- const total = this.db.prepare(`SELECT COUNT(*) AS c FROM files WHERE deleted = 0`).get().c;
1022
- return {
1023
- files: rows.map((row) => serializeFile(row)),
1024
- paging: {
1025
- count,
1026
- total,
1027
- page,
1028
- pages: total === 0 ? 0 : Math.ceil(total / count),
1029
- },
1030
- // files.list paginates via `paging` (page/pages), not a cursor. Real Slack
1031
- // does NOT return a `response_metadata.next_cursor` envelope on files.list,
1032
- // so the twin omits it rather than emitting an empty-cursor stub that diffs
1033
- // as a twin-only field (FDRS-473 Kind B).
1034
- };
1035
- }
1036
- filesDelete(args, actor, onDelta = NOOP) {
1037
- void actor;
1038
- const before = this.db.prepare(`SELECT * FROM files WHERE id = ?`).get(args.file);
1039
- if (!before)
1040
- slackError("file_not_found", 404);
1041
- const out = this.db.transaction(() => {
1042
- this.db.prepare(`UPDATE files SET deleted = 1 WHERE id = ?`).run(args.file);
1043
- const after = this.db.prepare(`SELECT * FROM files WHERE id = ?`).get(args.file);
1044
- onDelta({ before, after });
1045
- return {};
1046
- })();
1047
- return out;
1048
- }
1049
- // ───────────────────────────────────────────────────────────────────────────
1050
- // Bookmarks
1051
- // ───────────────────────────────────────────────────────────────────────────
1052
- bookmarksAdd(args, actor, onDelta = NOOP) {
1053
- const channel = this.requireChannel(args.channel_id);
1054
- if (!args.title)
1055
- slackError("invalid_arguments", 400);
1056
- if (!args.link && (!args.type || args.type === "link"))
1057
- slackError("invalid_arguments", 400);
1058
- const acting = this.resolveActorUser(actor);
1059
- const id = this.allocId(this.requireWorkspace().id, "Bk");
1060
- const out = this.db.transaction(() => {
1061
- this.db
1062
- .prepare(`INSERT INTO bookmarks (id, channel_id, title, link, emoji, type, created_by, created_at)
1063
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1064
- .run(id, channel.id, args.title, args.link ?? "", args.emoji ?? null, args.type ?? "link", acting.id, nowIso());
1065
- const after = this.db.prepare(`SELECT * FROM bookmarks WHERE id = ?`).get(id);
1066
- onDelta({ before: null, after });
1067
- return { bookmark: serializeBookmark(after) };
1068
- })();
1069
- return out;
1070
- }
1071
- bookmarksRemove(args, actor, onDelta = NOOP) {
1072
- void actor;
1073
- const channel = this.requireChannel(args.channel_id);
1074
- const before = this.db
1075
- .prepare(`SELECT * FROM bookmarks WHERE id = ? AND channel_id = ?`)
1076
- .get(args.bookmark_id, channel.id);
1077
- if (!before)
1078
- slackError("no_bookmark_found", 404);
1079
- const out = this.db.transaction(() => {
1080
- this.db.prepare(`DELETE FROM bookmarks WHERE id = ?`).run(before.id);
1081
- onDelta({ before, after: null });
1082
- return {};
1083
- })();
1084
- return out;
1085
- }
1086
- bookmarksList(args) {
1087
- const channel = this.requireChannel(args.channel_id);
1088
- const rows = this.db
1089
- .prepare(`SELECT * FROM bookmarks WHERE channel_id = ? ORDER BY created_at`)
1090
- .all(channel.id);
1091
- return { bookmarks: rows.map((row) => serializeBookmark(row)) };
1092
- }
1093
- // ───────────────────────────────────────────────────────────────────────────
1094
- // Team
1095
- // ───────────────────────────────────────────────────────────────────────────
1096
- teamInfo(args) {
1097
- void args;
1098
- return { team: serializeWorkspace(this.requireWorkspace()) };
1099
- }
1100
- // ───────────────────────────────────────────────────────────────────────────
1101
- // Private helpers
1102
- // ───────────────────────────────────────────────────────────────────────────
1103
- firstWorkspace() {
1104
- return this.db.prepare(`SELECT * FROM workspaces LIMIT 1`).get();
1105
- }
1106
- requireWorkspace() {
1107
- const w = this.firstWorkspace();
1108
- if (!w)
1109
- slackError("team_not_found", 404);
1110
- return w;
1111
- }
1112
- allChannels() {
1113
- return this.db.prepare(`SELECT * FROM channels ORDER BY created_at, id`).all();
1114
- }
1115
- allocId(workspaceId, prefix) {
1116
- const row = this.db
1117
- .prepare(`UPDATE workspaces SET entity_counter = entity_counter + 1 WHERE id = ? RETURNING entity_counter`)
1118
- .get(workspaceId);
1119
- if (!row)
1120
- throw new Error(`workspace ${workspaceId} not found for id allocation`);
1121
- return `${prefix}${String(row.entity_counter).padStart(6, "0")}`;
1122
- }
1123
- allocMessageTs(channelId) {
1124
- // Real Slack ts is workspace-globally-unique. Increment the workspace
1125
- // counter (not the channel counter) so two channels' first messages
1126
- // get distinct ts values. The channels.ts_counter column is kept for
1127
- // back-compat but no longer drives allocation.
1128
- const channel = this.db
1129
- .prepare(`SELECT team_id FROM channels WHERE id = ?`)
1130
- .get(channelId);
1131
- if (!channel)
1132
- throw new Error(`channel ${channelId} not found for ts allocation`);
1133
- const row = this.db
1134
- .prepare(`UPDATE workspaces SET ts_counter = ts_counter + 1 WHERE id = ? RETURNING ts_counter`)
1135
- .get(channel.team_id);
1136
- if (!row)
1137
- throw new Error(`workspace ${channel.team_id} not found for ts allocation`);
1138
- return `${tsBaseSeconds()}.${padTsCounter(row.ts_counter)}`;
1139
- }
1140
- resolveSeedUserId(ref, map) {
1141
- if (!ref)
1142
- return undefined;
1143
- return map.get(ref) ?? ref;
1144
- }
1145
- resolveUser(ref) {
1146
- if (!ref)
1147
- return undefined;
1148
- const byId = this.db.prepare(`SELECT * FROM users WHERE id = ?`).get(ref);
1149
- if (byId)
1150
- return byId;
1151
- const workspace = this.firstWorkspace();
1152
- if (!workspace)
1153
- return undefined;
1154
- const byName = this.db
1155
- .prepare(`SELECT * FROM users WHERE team_id = ? AND name = ?`)
1156
- .get(workspace.id, ref);
1157
- if (byName)
1158
- return byName;
1159
- if (ref.includes("@")) {
1160
- const byEmail = this.db
1161
- .prepare(`SELECT * FROM users WHERE team_id = ? AND email = ?`)
1162
- .get(workspace.id, ref);
1163
- if (byEmail)
1164
- return byEmail;
1165
- }
1166
- return undefined;
1167
- }
1168
- resolveActorUser(actor) {
1169
- if (actor.login) {
1170
- const user = this.resolveUser(actor.login);
1171
- if (!user)
1172
- slackError("user_not_found", 404);
1173
- return user;
1174
- }
1175
- const defaultUser = this.resolveUser("pome-agent");
1176
- if (defaultUser)
1177
- return defaultUser;
1178
- const fallback = this.db.prepare(`SELECT * FROM users WHERE id = ?`).get(DEFAULT_BOT_USER);
1179
- if (fallback)
1180
- return fallback;
1181
- slackError("user_not_found", 404);
1182
- }
1183
- channelRequiresMembership(channel) {
1184
- return Boolean(channel.is_private) || Boolean(channel.is_im) || Boolean(channel.is_mpim);
1185
- }
1186
- assertChannelMember(channel, userId) {
1187
- if (!this.channelRequiresMembership(channel))
1188
- return;
1189
- const member = this.db
1190
- .prepare(`SELECT 1 FROM channel_members WHERE channel_id = ? AND user_id = ?`)
1191
- .get(channel.id, userId);
1192
- if (!member)
1193
- slackError("not_in_channel", 400);
1194
- }
1195
- assertChannelMemberForActor(channel, actor) {
1196
- this.assertChannelMember(channel, this.resolveActorUser(actor).id);
1197
- }
1198
- requireChannel(ref) {
1199
- const byId = this.db.prepare(`SELECT * FROM channels WHERE id = ?`).get(ref);
1200
- if (byId)
1201
- return byId;
1202
- const workspace = this.firstWorkspace();
1203
- if (workspace) {
1204
- const cleaned = ref.startsWith("#") ? ref.slice(1) : ref;
1205
- const byName = this.db
1206
- .prepare(`SELECT * FROM channels WHERE team_id = ? AND name = ?`)
1207
- .get(workspace.id, cleaned);
1208
- if (byName)
1209
- return byName;
1210
- }
1211
- notFound("channel_not_found");
1212
- }
1213
- requireChannelRow(id) {
1214
- const row = this.db.prepare(`SELECT * FROM channels WHERE id = ?`).get(id);
1215
- if (!row)
1216
- slackError("channel_not_found", 404);
1217
- return row;
1218
- }
1219
- requireMessageRow(channelId, ts) {
1220
- const row = this.db
1221
- .prepare(`SELECT * FROM messages WHERE channel_id = ? AND ts = ?`)
1222
- .get(channelId, ts);
1223
- if (!row)
1224
- slackError("message_not_found", 404);
1225
- return row;
1226
- }
1227
- requireScheduledMessage(id) {
1228
- const row = this.db.prepare(`SELECT * FROM scheduled_messages WHERE id = ?`).get(id);
1229
- if (!row)
1230
- slackError("scheduled_message_not_found", 404);
1231
- return row;
1232
- }
1233
- channelMemberIds(channelId) {
1234
- return this.db
1235
- .prepare(`SELECT user_id FROM channel_members WHERE channel_id = ? ORDER BY joined_at, user_id`)
1236
- .all(channelId).map((r) => r.user_id);
1237
- }
1238
- reactionsFor(channelId, ts) {
1239
- return this.db
1240
- .prepare(`SELECT * FROM reactions WHERE channel_id = ? AND message_ts = ? ORDER BY added_at, name, user_id`)
1241
- .all(channelId, ts);
1242
- }
1243
- findDirectChannelBySignature(teamId, dmSignature) {
1244
- const row = this.db
1245
- .prepare(`SELECT * FROM channels WHERE team_id = ? AND dm_signature = ?`)
1246
- .get(teamId, dmSignature);
1247
- return row;
1248
- }
1249
- userNameOf(id) {
1250
- const row = this.db.prepare(`SELECT name FROM users WHERE id = ?`).get(id);
1251
- return row?.name ?? id;
1252
- }
1253
- }
1254
- function clampLimit(value, max, fallback) {
1255
- if (!Number.isFinite(value) || value === undefined)
1256
- return fallback;
1257
- return Math.min(max, Math.max(1, Math.floor(value)));
1258
- }
1259
- function safeParseJson(raw) {
1260
- if (!raw)
1261
- return {};
1262
- try {
1263
- return JSON.parse(raw);
1264
- }
1265
- catch {
1266
- return {};
1267
- }
1268
- }
1269
- function sanitizeJsonString(raw, fallback) {
1270
- try {
1271
- const parsed = JSON.parse(raw);
1272
- return JSON.stringify(parsed);
1273
- }
1274
- catch {
1275
- return fallback;
1276
- }
1277
- }
1278
- function normalizeReactionName(raw) {
1279
- return raw.trim().replace(/^:|:$/g, "");
1280
- }
1281
- function filetypeMimetype(filetype) {
1282
- const mapping = {
1283
- text: "text/plain",
1284
- markdown: "text/markdown",
1285
- json: "application/json",
1286
- pdf: "application/pdf",
1287
- png: "image/png",
1288
- jpg: "image/jpeg",
1289
- jpeg: "image/jpeg",
1290
- gif: "image/gif",
1291
- };
1292
- return mapping[filetype] ?? "application/octet-stream";
1293
- }