kimaki 0.4.81 → 0.4.83

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/dist/anthropic-auth-plugin.js +7 -0
  3. package/dist/cli.js +44 -509
  4. package/dist/commands/abort.js +5 -16
  5. package/dist/commands/action-buttons.js +3 -3
  6. package/dist/commands/add-project.js +1 -1
  7. package/dist/commands/ask-question.js +3 -3
  8. package/dist/commands/context-usage.js +1 -1
  9. package/dist/commands/create-new-project.js +1 -1
  10. package/dist/commands/fork.js +11 -8
  11. package/dist/commands/merge-worktree.js +1 -1
  12. package/dist/commands/new-worktree.js +63 -44
  13. package/dist/commands/remove-project.js +1 -1
  14. package/dist/commands/restart-opencode-server.js +61 -8
  15. package/dist/commands/resume.js +11 -8
  16. package/dist/commands/session.js +1 -1
  17. package/dist/commands/undo-redo.js +91 -7
  18. package/dist/commands/user-command.js +1 -1
  19. package/dist/condense-memory.js +1 -1
  20. package/dist/context-awareness-plugin.js +1 -1
  21. package/dist/database.js +53 -2
  22. package/dist/db.js +6 -0
  23. package/dist/discord-bot.js +104 -90
  24. package/dist/discord-command-registration.js +512 -0
  25. package/dist/external-opencode-sync.js +538 -0
  26. package/dist/external-opencode-sync.test.js +151 -0
  27. package/dist/gateway-proxy.e2e.test.js +51 -0
  28. package/dist/genai.js +1 -1
  29. package/dist/generated/enums.js +4 -0
  30. package/dist/generated/internal/class.js +4 -4
  31. package/dist/generated/internal/prismaNamespace.js +1 -0
  32. package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
  33. package/dist/generated/models/external_session_pending_prompts.js +1 -0
  34. package/dist/hrana-server.js +14 -285
  35. package/dist/hrana-server.test.js +4 -2
  36. package/dist/ipc-polling.js +4 -3
  37. package/dist/ipc-tools-plugin.js +1 -1
  38. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
  39. package/dist/kimaki-opencode-plugin.js +15 -0
  40. package/dist/kimaki-opencode-plugin.test.js +98 -0
  41. package/dist/kitty-graphics-parser.js +3 -0
  42. package/dist/kitty-graphics-parser.test.js +276 -0
  43. package/dist/kitty-graphics-plugin.js +3 -0
  44. package/dist/markdown.js +4 -4
  45. package/dist/markdown.test.js +1 -1
  46. package/dist/message-formatting.js +54 -15
  47. package/dist/openai-realtime.js +9 -13
  48. package/dist/opencode.js +29 -6
  49. package/dist/queue-advanced-e2e-setup.js +53 -0
  50. package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
  51. package/dist/queue-advanced-typing.e2e.test.js +9 -22
  52. package/dist/runtime-idle-sweeper.js +3 -1
  53. package/dist/sentry.js +1 -1
  54. package/dist/session-handler/event-stream-state.js +101 -7
  55. package/dist/session-handler/event-stream-state.test.js +7 -3
  56. package/dist/session-handler/thread-session-runtime.js +77 -9
  57. package/dist/system-message.js +6 -0
  58. package/dist/system-message.test.js +19 -0
  59. package/dist/task-runner.js +1 -1
  60. package/dist/thread-message-queue.e2e.test.js +8 -14
  61. package/dist/tools.js +1 -1
  62. package/dist/undo-redo.e2e.test.js +20 -25
  63. package/package.json +9 -6
  64. package/schema.prisma +6 -0
  65. package/skills/npm-package/SKILL.md +1 -0
  66. package/skills/proxyman/SKILL.md +215 -0
  67. package/skills/usecomputer/SKILL.md +339 -0
  68. package/src/ai-tool-to-genai.ts +1 -0
  69. package/src/anthropic-auth-plugin.ts +7 -0
  70. package/src/cli.ts +46 -670
  71. package/src/commands/abort.ts +6 -16
  72. package/src/commands/action-buttons.ts +5 -1
  73. package/src/commands/add-project.ts +1 -1
  74. package/src/commands/ask-question.ts +5 -2
  75. package/src/commands/context-usage.ts +1 -1
  76. package/src/commands/create-new-project.ts +1 -1
  77. package/src/commands/fork.ts +12 -11
  78. package/src/commands/merge-worktree.ts +1 -1
  79. package/src/commands/new-worktree.ts +74 -55
  80. package/src/commands/remove-project.ts +1 -1
  81. package/src/commands/restart-opencode-server.ts +67 -7
  82. package/src/commands/resume.ts +12 -10
  83. package/src/commands/session.ts +1 -1
  84. package/src/commands/undo-redo.ts +108 -10
  85. package/src/commands/user-command.ts +1 -1
  86. package/src/condense-memory.ts +1 -1
  87. package/src/context-awareness-plugin.ts +1 -1
  88. package/src/database.ts +72 -3
  89. package/src/db.ts +8 -0
  90. package/src/discord-bot.ts +125 -97
  91. package/src/discord-command-registration.ts +678 -0
  92. package/src/external-opencode-sync.ts +760 -0
  93. package/src/gateway-proxy.e2e.test.ts +63 -0
  94. package/src/genai.ts +1 -1
  95. package/src/generated/commonInputTypes.ts +34 -0
  96. package/src/generated/enums.ts +8 -0
  97. package/src/generated/internal/class.ts +4 -4
  98. package/src/generated/internal/prismaNamespace.ts +8 -0
  99. package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
  100. package/src/generated/models/thread_sessions.ts +53 -1
  101. package/src/hrana-server.test.ts +8 -2
  102. package/src/hrana-server.ts +18 -390
  103. package/src/ipc-polling.ts +4 -3
  104. package/src/ipc-tools-plugin.ts +1 -1
  105. package/src/{opencode-plugin-loading.e2e.test.ts → kimaki-opencode-plugin-loading.e2e.test.ts} +8 -1
  106. package/src/{opencode-plugin.ts → kimaki-opencode-plugin.ts} +2 -0
  107. package/src/markdown.test.ts +1 -1
  108. package/src/markdown.ts +4 -4
  109. package/src/message-formatting.ts +66 -17
  110. package/src/openai-realtime.ts +6 -10
  111. package/src/opencode.ts +32 -8
  112. package/src/queue-advanced-e2e-setup.ts +55 -0
  113. package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
  114. package/src/queue-advanced-typing.e2e.test.ts +9 -22
  115. package/src/runtime-idle-sweeper.ts +3 -1
  116. package/src/schema.sql +1 -0
  117. package/src/sentry.ts +1 -1
  118. package/src/session-handler/event-stream-state.test.ts +7 -2
  119. package/src/session-handler/event-stream-state.ts +128 -7
  120. package/src/session-handler/thread-runtime-state.ts +5 -0
  121. package/src/session-handler/thread-session-runtime.ts +93 -11
  122. package/src/system-message.ts +11 -0
  123. package/src/task-runner.ts +1 -1
  124. package/src/thread-message-queue.e2e.test.ts +8 -14
  125. package/src/tools.ts +1 -1
  126. package/src/undo-redo.e2e.test.ts +28 -26
  127. package/skills/jitter/node_modules/.bin/esbuild +0 -21
  128. package/skills/jitter/node_modules/.bin/tsc +0 -21
  129. package/skills/jitter/node_modules/.bin/tsserver +0 -21
  130. package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
  131. package/skills/jitter/node_modules/typescript/README.md +0 -50
  132. package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
  133. package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
  134. package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
  135. package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
  136. package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
  137. package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
  138. package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  139. package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  140. package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  141. package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  142. package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  143. package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  144. package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  145. package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  146. package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
  147. package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
  148. package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
  149. package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
  150. package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
  151. package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
  152. package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
  153. package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
  154. package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
  155. package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
  156. package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
  157. package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
  158. package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
  159. package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
  160. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
  161. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
  162. package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
  163. package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
  164. package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
  165. package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
  166. package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
  167. package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
  168. package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
  169. package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
  170. package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
  171. package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
  172. package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
  173. package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
  174. package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
  175. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
  176. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
  177. package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
  178. package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
  179. package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
  180. package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
  181. package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
  182. package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
  183. package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
  184. package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
  185. package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
  186. package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
  187. package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
  188. package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
  189. package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
  190. package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
  191. package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
  192. package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
  193. package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
  194. package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
  195. package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
  196. package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
  197. package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
  198. package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
  199. package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
  200. package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
  201. package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
  202. package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
  203. package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
  204. package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
  205. package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
  206. package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
  207. package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
  208. package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
  209. package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
  210. package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
  211. package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
  212. package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
  213. package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
  214. package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
  215. package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
  216. package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
  217. package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
  218. package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
  219. package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
  220. package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
  221. package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
  222. package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
  223. package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
  224. package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
  225. package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
  226. package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
  227. package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
  228. package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
  229. package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
  230. package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
  231. package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
  232. package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
  233. package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
  234. package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
  235. package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
  236. package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
  237. package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
  238. package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
  239. package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
  240. package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
  241. package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
  242. package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
  243. package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
  244. package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
  245. package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
  246. package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  247. package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  248. package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  249. package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  250. package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
  251. package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
  252. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
  253. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  254. package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
  255. package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
  256. package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
  257. package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
  258. package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
  259. package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  260. package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  261. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
  262. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
  263. package/skills/jitter/node_modules/typescript/package.json +0 -120
  264. /package/src/{opencode-plugin.test.ts → kimaki-opencode-plugin.test.ts} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kimaki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * Anthropic OAuth authentication plugin for OpenCode.
3
3
  *
4
+ * If you're copy-pasting this plugin into your OpenCode config folder,
5
+ * you need to install the runtime dependencies first:
6
+ *
7
+ * cd ~/.config/opencode
8
+ * bun init -y
9
+ * bun add @openauthjs/openauth proper-lockfile
10
+ *
4
11
  * Handles two concerns:
5
12
  * 1. OAuth login + token refresh (PKCE flow against claude.ai)
6
13
  * 2. Request/response rewriting (tool names, system prompt, beta headers)
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { z } from 'zod';
7
7
  import { intro, outro, text, password, note, cancel, isCancel, confirm, log, multiselect, select, spinner, } from '@clack/prompts';
8
8
  import { deduplicateByKey, generateBotInstallUrl, generateDiscordInstallUrlForBot, KIMAKI_GATEWAY_APP_ID, KIMAKI_WEBSITE_URL, abbreviatePath, } from './utils.js';
9
9
  import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, ensureKimakiCategory, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
10
- import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getPrisma, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, } from './database.js';
10
+ import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getPrisma, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, } from './database.js';
11
11
  import { ShareMarkdown } from './markdown.js';
12
12
  import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts, } from './session-search.js';
13
13
  import { formatWorktreeName } from './commands/new-worktree.js';
@@ -16,7 +16,7 @@ import { sendWelcomeMessage } from './onboarding-welcome.js';
16
16
  import { buildOpencodeEventLogLine } from './session-handler/opencode-session-event-log.js';
17
17
  import { selectResolvedCommand } from './opencode-command.js';
18
18
  import yaml from 'js-yaml';
19
- import { Events, ChannelType, ActivityType, Routes, SlashCommandBuilder, AttachmentBuilder, } from 'discord.js';
19
+ import { Events, ChannelType, ActivityType, Routes, AttachmentBuilder, } from 'discord.js';
20
20
  import { createDiscordRest, discordApiUrl, getDiscordRestApiUrl, getGatewayProxyRestBaseUrl, getInternetReachableBaseUrl } from './discord-urls.js';
21
21
  import crypto from 'node:crypto';
22
22
  import path from 'node:path';
@@ -27,7 +27,6 @@ import { initSentry, notifyError } from './sentry.js';
27
27
  import { archiveThread, uploadFilesToDiscord, stripMentions, } from './discord-utils.js';
28
28
  import { spawn, execSync } from 'node:child_process';
29
29
  import { setDataDir, getDataDir, getProjectsDir, } from './config.js';
30
- import { sanitizeAgentName, buildQuickAgentCommandDescription, } from './commands/agent.js';
31
30
  import { execAsync } from './worktrees.js';
32
31
  import { backgroundUpgradeKimaki, upgrade, getCurrentVersion, } from './upgrade.js';
33
32
  import { startHranaServer } from './hrana-server.js';
@@ -437,509 +436,8 @@ function startCaffeinate() {
437
436
  }
438
437
  const cli = goke('kimaki');
439
438
  process.title = 'kimaki';
440
- // Commands to skip when registering user commands (reserved names)
441
- const SKIP_USER_COMMANDS = ['init'];
442
- function getDiscordCommandSuffix(command) {
443
- if (command.source === 'skill') {
444
- return '-skill';
445
- }
446
- if (command.source === 'mcp') {
447
- return '-mcp-prompt';
448
- }
449
- return '-cmd';
450
- }
451
439
  import { store } from './store.js';
452
- function isDiscordCommandSummary(value) {
453
- if (typeof value !== 'object' || value === null) {
454
- return false;
455
- }
456
- const id = Reflect.get(value, 'id');
457
- const name = Reflect.get(value, 'name');
458
- return typeof id === 'string' && typeof name === 'string';
459
- }
460
- async function deleteLegacyGlobalCommands({ rest, appId, commandNames, }) {
461
- try {
462
- const response = await rest.get(Routes.applicationCommands(appId));
463
- if (!Array.isArray(response)) {
464
- cliLogger.warn('COMMANDS: Unexpected global command payload while cleaning legacy global commands');
465
- return;
466
- }
467
- const legacyGlobalCommands = response
468
- .filter(isDiscordCommandSummary)
469
- .filter((command) => {
470
- return commandNames.has(command.name);
471
- });
472
- if (legacyGlobalCommands.length === 0) {
473
- return;
474
- }
475
- const deletionResults = await Promise.allSettled(legacyGlobalCommands.map(async (command) => {
476
- await rest.delete(Routes.applicationCommand(appId, command.id));
477
- return command;
478
- }));
479
- const failedDeletions = deletionResults.filter((result) => {
480
- return result.status === 'rejected';
481
- });
482
- if (failedDeletions.length > 0) {
483
- cliLogger.warn(`COMMANDS: Failed to delete ${failedDeletions.length} legacy global command(s)`);
484
- }
485
- const deletedCount = deletionResults.length - failedDeletions.length;
486
- if (deletedCount > 0) {
487
- cliLogger.info(`COMMANDS: Deleted ${deletedCount} legacy global command(s) to avoid guild/global duplicates`);
488
- }
489
- }
490
- catch (error) {
491
- cliLogger.warn(`COMMANDS: Could not clean legacy global commands: ${error instanceof Error ? error.stack : String(error)}`);
492
- }
493
- }
494
- // Discord slash command descriptions must be 1-100 chars.
495
- // Truncate to 100 so @sapphire/shapeshift validation never throws.
496
- function truncateCommandDescription(description) {
497
- return description.slice(0, 100);
498
- }
499
- async function registerCommands({ token, appId, guildIds, userCommands = [], agents = [], }) {
500
- const commands = [
501
- new SlashCommandBuilder()
502
- .setName('resume')
503
- .setDescription(truncateCommandDescription('Resume an existing OpenCode session'))
504
- .addStringOption((option) => {
505
- option
506
- .setName('session')
507
- .setDescription(truncateCommandDescription('The session to resume'))
508
- .setRequired(true)
509
- .setAutocomplete(true);
510
- return option;
511
- })
512
- .setDMPermission(false)
513
- .toJSON(),
514
- new SlashCommandBuilder()
515
- .setName('new-session')
516
- .setDescription(truncateCommandDescription('Start a new OpenCode session'))
517
- .addStringOption((option) => {
518
- option
519
- .setName('prompt')
520
- .setDescription(truncateCommandDescription('Prompt content for the session'))
521
- .setRequired(true);
522
- return option;
523
- })
524
- .addStringOption((option) => {
525
- option
526
- .setName('files')
527
- .setDescription(truncateCommandDescription('Files to mention (comma or space separated; autocomplete)'))
528
- .setAutocomplete(true)
529
- .setMaxLength(6000);
530
- return option;
531
- })
532
- .addStringOption((option) => {
533
- option
534
- .setName('agent')
535
- .setDescription(truncateCommandDescription('Agent to use for this session'))
536
- .setAutocomplete(true);
537
- return option;
538
- })
539
- .setDMPermission(false)
540
- .toJSON(),
541
- new SlashCommandBuilder()
542
- .setName('new-worktree')
543
- .setDescription(truncateCommandDescription('Create a git worktree branch from origin/HEAD (or main). Optionally pick a base branch.'))
544
- .addStringOption((option) => {
545
- option
546
- .setName('name')
547
- .setDescription(truncateCommandDescription('Name for worktree (optional in threads - uses thread name)'))
548
- .setRequired(false);
549
- return option;
550
- })
551
- .addStringOption((option) => {
552
- option
553
- .setName('base-branch')
554
- .setDescription(truncateCommandDescription('Branch to create the worktree from (default: origin/HEAD or main)'))
555
- .setRequired(false)
556
- .setAutocomplete(true);
557
- return option;
558
- })
559
- .setDMPermission(false)
560
- .toJSON(),
561
- new SlashCommandBuilder()
562
- .setName('merge-worktree')
563
- .setDescription(truncateCommandDescription('Squash-merge worktree into default branch. Aborts if main has uncommitted changes.'))
564
- .addStringOption((option) => {
565
- option
566
- .setName('target-branch')
567
- .setDescription(truncateCommandDescription('Branch to merge into (default: origin/HEAD or main)'))
568
- .setRequired(false)
569
- .setAutocomplete(true);
570
- return option;
571
- })
572
- .setDMPermission(false)
573
- .toJSON(),
574
- new SlashCommandBuilder()
575
- .setName('toggle-worktrees')
576
- .setDescription(truncateCommandDescription('Toggle automatic git worktree creation for new sessions in this channel'))
577
- .setDMPermission(false)
578
- .toJSON(),
579
- new SlashCommandBuilder()
580
- .setName('worktrees')
581
- .setDescription(truncateCommandDescription('List all active worktree sessions'))
582
- .setDMPermission(false)
583
- .toJSON(),
584
- new SlashCommandBuilder()
585
- .setName('tasks')
586
- .setDescription(truncateCommandDescription('List scheduled tasks created via send --send-at'))
587
- .addBooleanOption((option) => {
588
- return option
589
- .setName('all')
590
- .setDescription(truncateCommandDescription('Include completed, cancelled, and failed tasks'));
591
- })
592
- .setDMPermission(false)
593
- .toJSON(),
594
- new SlashCommandBuilder()
595
- .setName('toggle-mention-mode')
596
- .setDescription(truncateCommandDescription('Toggle mention-only mode (bot only responds when @mentioned)'))
597
- .setDMPermission(false)
598
- .toJSON(),
599
- new SlashCommandBuilder()
600
- .setName('add-project')
601
- .setDescription(truncateCommandDescription('Create Discord channels for a project. Use `npx kimaki project add` for unlisted projects'))
602
- .addStringOption((option) => {
603
- option
604
- .setName('project')
605
- .setDescription(truncateCommandDescription('Recent OpenCode projects. Use `npx kimaki project add` if not listed'))
606
- .setRequired(true)
607
- .setAutocomplete(true);
608
- return option;
609
- })
610
- .setDMPermission(false)
611
- .toJSON(),
612
- new SlashCommandBuilder()
613
- .setName('remove-project')
614
- .setDescription(truncateCommandDescription('Remove Discord channels for a project'))
615
- .addStringOption((option) => {
616
- option
617
- .setName('project')
618
- .setDescription(truncateCommandDescription('Select a project to remove'))
619
- .setRequired(true)
620
- .setAutocomplete(true);
621
- return option;
622
- })
623
- .setDMPermission(false)
624
- .toJSON(),
625
- new SlashCommandBuilder()
626
- .setName('create-new-project')
627
- .setDescription(truncateCommandDescription('Create a new project folder, initialize git, and start a session'))
628
- .addStringOption((option) => {
629
- option
630
- .setName('name')
631
- .setDescription(truncateCommandDescription('Name for the new project folder'))
632
- .setRequired(true);
633
- return option;
634
- })
635
- .setDMPermission(false)
636
- .toJSON(),
637
- new SlashCommandBuilder()
638
- .setName('abort')
639
- .setDescription(truncateCommandDescription('Abort the current OpenCode request in this thread'))
640
- .setDMPermission(false)
641
- .toJSON(),
642
- new SlashCommandBuilder()
643
- .setName('compact')
644
- .setDescription(truncateCommandDescription('Compact the session context by summarizing conversation history'))
645
- .setDMPermission(false)
646
- .toJSON(),
647
- new SlashCommandBuilder()
648
- .setName('stop')
649
- .setDescription(truncateCommandDescription('Abort the current OpenCode request in this thread'))
650
- .setDMPermission(false)
651
- .toJSON(),
652
- new SlashCommandBuilder()
653
- .setName('share')
654
- .setDescription(truncateCommandDescription('Share the current session as a public URL'))
655
- .setDMPermission(false)
656
- .toJSON(),
657
- new SlashCommandBuilder()
658
- .setName('diff')
659
- .setDescription(truncateCommandDescription('Show git diff as a shareable URL'))
660
- .setDMPermission(false)
661
- .toJSON(),
662
- new SlashCommandBuilder()
663
- .setName('fork')
664
- .setDescription(truncateCommandDescription('Fork the session from a past user message'))
665
- .setDMPermission(false)
666
- .toJSON(),
667
- new SlashCommandBuilder()
668
- .setName('model')
669
- .setDescription(truncateCommandDescription('Set the preferred model for this channel or session'))
670
- .setDMPermission(false)
671
- .toJSON(),
672
- new SlashCommandBuilder()
673
- .setName('model-variant')
674
- .setDescription(truncateCommandDescription('Quickly change the thinking level variant for the current model'))
675
- .setDMPermission(false)
676
- .toJSON(),
677
- new SlashCommandBuilder()
678
- .setName('unset-model-override')
679
- .setDescription(truncateCommandDescription('Remove model override and use default instead'))
680
- .setDMPermission(false)
681
- .toJSON(),
682
- new SlashCommandBuilder()
683
- .setName('login')
684
- .setDescription(truncateCommandDescription('Authenticate with an AI provider (OAuth or API key). Use this instead of /connect'))
685
- .setDMPermission(false)
686
- .toJSON(),
687
- new SlashCommandBuilder()
688
- .setName('agent')
689
- .setDescription(truncateCommandDescription('Set the preferred agent for this channel or session'))
690
- .setDMPermission(false)
691
- .toJSON(),
692
- new SlashCommandBuilder()
693
- .setName('queue')
694
- .setDescription(truncateCommandDescription('Queue a message to be sent after the current response finishes'))
695
- .addStringOption((option) => {
696
- option
697
- .setName('message')
698
- .setDescription(truncateCommandDescription('The message to queue'))
699
- .setRequired(true);
700
- return option;
701
- })
702
- .setDMPermission(false)
703
- .toJSON(),
704
- new SlashCommandBuilder()
705
- .setName('clear-queue')
706
- .setDescription(truncateCommandDescription('Clear all queued messages in this thread'))
707
- .setDMPermission(false)
708
- .toJSON(),
709
- new SlashCommandBuilder()
710
- .setName('queue-command')
711
- .setDescription(truncateCommandDescription('Queue a user command to run after the current response finishes'))
712
- .addStringOption((option) => {
713
- option
714
- .setName('command')
715
- .setDescription(truncateCommandDescription('The command to run'))
716
- .setRequired(true)
717
- .setAutocomplete(true);
718
- return option;
719
- })
720
- .addStringOption((option) => {
721
- option
722
- .setName('arguments')
723
- .setDescription(truncateCommandDescription('Arguments to pass to the command'))
724
- .setRequired(false);
725
- return option;
726
- })
727
- .setDMPermission(false)
728
- .toJSON(),
729
- new SlashCommandBuilder()
730
- .setName('undo')
731
- .setDescription(truncateCommandDescription('Undo the last assistant message (revert file changes)'))
732
- .setDMPermission(false)
733
- .toJSON(),
734
- new SlashCommandBuilder()
735
- .setName('redo')
736
- .setDescription(truncateCommandDescription('Redo previously undone changes'))
737
- .setDMPermission(false)
738
- .toJSON(),
739
- new SlashCommandBuilder()
740
- .setName('verbosity')
741
- .setDescription(truncateCommandDescription('Set output verbosity for this channel'))
742
- .setDMPermission(false)
743
- .toJSON(),
744
- new SlashCommandBuilder()
745
- .setName('restart-opencode-server')
746
- .setDescription(truncateCommandDescription('Restart the shared opencode server (fixes state/auth/plugins)'))
747
- .setDMPermission(false)
748
- .toJSON(),
749
- new SlashCommandBuilder()
750
- .setName('run-shell-command')
751
- .setDescription(truncateCommandDescription('Run a shell command in the project directory. Tip: prefix messages with ! as shortcut'))
752
- .addStringOption((option) => {
753
- option
754
- .setName('command')
755
- .setDescription(truncateCommandDescription('Command to run'))
756
- .setRequired(true);
757
- return option;
758
- })
759
- .setDMPermission(false)
760
- .toJSON(),
761
- new SlashCommandBuilder()
762
- .setName('context-usage')
763
- .setDescription(truncateCommandDescription('Show token usage and context window percentage for this session'))
764
- .setDMPermission(false)
765
- .toJSON(),
766
- new SlashCommandBuilder()
767
- .setName('session-id')
768
- .setDescription(truncateCommandDescription('Show current session ID and opencode attach command for this thread'))
769
- .setDMPermission(false)
770
- .toJSON(),
771
- new SlashCommandBuilder()
772
- .setName('memory-snapshot')
773
- .setDescription(truncateCommandDescription('Write a V8 heap snapshot to disk for memory debugging'))
774
- .setDMPermission(false)
775
- .toJSON(),
776
- new SlashCommandBuilder()
777
- .setName('upgrade-and-restart')
778
- .setDescription(truncateCommandDescription('Upgrade kimaki to the latest version and restart the bot'))
779
- .setDMPermission(false)
780
- .toJSON(),
781
- new SlashCommandBuilder()
782
- .setName('transcription-key')
783
- .setDescription(truncateCommandDescription('Set API key for voice message transcription (OpenAI or Gemini)'))
784
- .setDMPermission(false)
785
- .toJSON(),
786
- new SlashCommandBuilder()
787
- .setName('mcp')
788
- .setDescription(truncateCommandDescription('List and manage MCP servers for this project'))
789
- .setDMPermission(false)
790
- .toJSON(),
791
- new SlashCommandBuilder()
792
- .setName('screenshare')
793
- .setDescription(truncateCommandDescription('Start screen sharing via VNC tunnel (auto-stops after 1 hour)'))
794
- .setDMPermission(false)
795
- .toJSON(),
796
- new SlashCommandBuilder()
797
- .setName('screenshare-stop')
798
- .setDescription(truncateCommandDescription('Stop screen sharing'))
799
- .setDMPermission(false)
800
- .toJSON(),
801
- ];
802
- // Add user-defined commands with source-based suffixes (-cmd / -skill)
803
- // Also populate registeredUserCommands in the store for /queue-command autocomplete
804
- const newRegisteredCommands = [];
805
- for (const cmd of userCommands) {
806
- if (SKIP_USER_COMMANDS.includes(cmd.name)) {
807
- continue;
808
- }
809
- // Sanitize command name: oh-my-opencode uses MCP commands with colons and slashes,
810
- // which Discord doesn't allow in command names.
811
- // Discord command names: lowercase, alphanumeric and hyphens only, must start with letter/number.
812
- const sanitizedName = cmd.name
813
- .toLowerCase()
814
- .replace(/[:/]/g, '-') // Replace : and / with hyphens first
815
- .replace(/[^a-z0-9-]/g, '-') // Replace any other non-alphanumeric chars
816
- .replace(/-+/g, '-') // Collapse multiple hyphens
817
- .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
818
- // Skip if sanitized name is empty - would create invalid command name like "-cmd"
819
- if (!sanitizedName) {
820
- continue;
821
- }
822
- const commandSuffix = getDiscordCommandSuffix(cmd);
823
- // Truncate base name before appending suffix so the suffix is never
824
- // lost to Discord's 32-char command name limit.
825
- const baseName = sanitizedName.slice(0, 32 - commandSuffix.length);
826
- const commandName = `${baseName}${commandSuffix}`;
827
- const description = cmd.description || `Run /${cmd.name} command`;
828
- newRegisteredCommands.push({
829
- name: cmd.name,
830
- discordCommandName: commandName,
831
- description,
832
- source: cmd.source,
833
- });
834
- commands.push(new SlashCommandBuilder()
835
- .setName(commandName)
836
- .setDescription(truncateCommandDescription(description))
837
- .addStringOption((option) => {
838
- option
839
- .setName('arguments')
840
- .setDescription(truncateCommandDescription('Arguments to pass to the command'))
841
- .setRequired(false);
842
- return option;
843
- })
844
- .setDMPermission(false)
845
- .toJSON());
846
- }
847
- store.setState({ registeredUserCommands: newRegisteredCommands });
848
- // Add agent-specific quick commands like /plan-agent, /build-agent
849
- // Filter to primary/all mode agents (same as /agent command shows), excluding hidden agents
850
- const primaryAgents = agents.filter((a) => (a.mode === 'primary' || a.mode === 'all') && !a.hidden);
851
- for (const agent of primaryAgents) {
852
- const sanitizedName = sanitizeAgentName(agent.name);
853
- // Skip if sanitized name is empty or would create invalid command name
854
- // Discord command names must start with a lowercase letter or number
855
- if (!sanitizedName || !/^[a-z0-9]/.test(sanitizedName)) {
856
- continue;
857
- }
858
- // Truncate base name before appending suffix so the -agent suffix is never
859
- // lost to Discord's 32-char command name limit.
860
- const agentSuffix = '-agent';
861
- const agentBaseName = sanitizedName.slice(0, 32 - agentSuffix.length);
862
- const commandName = `${agentBaseName}${agentSuffix}`;
863
- const description = buildQuickAgentCommandDescription({
864
- agentName: agent.name,
865
- description: agent.description,
866
- });
867
- commands.push(new SlashCommandBuilder()
868
- .setName(commandName)
869
- .setDescription(truncateCommandDescription(description))
870
- .setDMPermission(false)
871
- .toJSON());
872
- }
873
- const rest = createDiscordRest(token);
874
- const uniqueGuildIds = Array.from(new Set(guildIds.filter((guildId) => guildId)));
875
- const guildCommandNames = new Set(commands
876
- .map((command) => {
877
- return command.name;
878
- })
879
- .filter((name) => {
880
- return typeof name === 'string';
881
- }));
882
- if (uniqueGuildIds.length === 0) {
883
- cliLogger.warn('COMMANDS: No guilds available, skipping slash command registration');
884
- return;
885
- }
886
- try {
887
- // PUT is a bulk overwrite: Discord matches by name, updates changed fields
888
- // (description, options, etc.) in place, creates new commands, and deletes
889
- // any not present in the body. No local diffing needed.
890
- const results = await Promise.allSettled(uniqueGuildIds.map(async (guildId) => {
891
- const response = await rest.put(Routes.applicationGuildCommands(appId, guildId), {
892
- body: commands,
893
- });
894
- const registeredCount = Array.isArray(response)
895
- ? response.length
896
- : commands.length;
897
- return { guildId, registeredCount };
898
- }));
899
- const failedGuilds = results
900
- .map((result, index) => {
901
- if (result.status === 'fulfilled') {
902
- return null;
903
- }
904
- return {
905
- guildId: uniqueGuildIds[index],
906
- error: result.reason instanceof Error
907
- ? result.reason.message
908
- : String(result.reason),
909
- };
910
- })
911
- .filter((value) => {
912
- return value !== null;
913
- });
914
- if (failedGuilds.length > 0) {
915
- failedGuilds.forEach((failure) => {
916
- cliLogger.warn(`COMMANDS: Failed to register slash commands for guild ${failure.guildId}: ${failure.error}`);
917
- });
918
- throw new Error(`Failed to register slash commands for ${failedGuilds.length} guild(s)`);
919
- }
920
- const successfulGuilds = results.length;
921
- const firstRegisteredCount = results[0];
922
- const registeredCommandCount = firstRegisteredCount && firstRegisteredCount.status === 'fulfilled'
923
- ? firstRegisteredCount.value.registeredCount
924
- : commands.length;
925
- // In gateway mode, global application routes (/applications/{app_id}/commands)
926
- // are denied by the proxy (DeniedWithoutGuild). Legacy global commands only
927
- // exist for self-hosted bots that previously registered commands globally.
928
- const isGateway = store.getState().discordBaseUrl !== 'https://discord.com';
929
- if (!isGateway) {
930
- await deleteLegacyGlobalCommands({
931
- rest,
932
- appId,
933
- commandNames: guildCommandNames,
934
- });
935
- }
936
- cliLogger.info(`COMMANDS: Successfully registered ${registeredCommandCount} slash commands for ${successfulGuilds} guild(s)`);
937
- }
938
- catch (error) {
939
- cliLogger.error('COMMANDS: Failed to register slash commands: ' + String(error));
940
- throw error;
941
- }
942
- }
440
+ import { registerCommands, SKIP_USER_COMMANDS } from './discord-command-registration.js';
943
441
  async function reconcileKimakiRole({ guild }) {
944
442
  try {
945
443
  const roles = await guild.roles.fetch();
@@ -2855,6 +2353,7 @@ cli
2855
2353
  cli
2856
2354
  .command('project list', 'List all registered projects with their Discord channels')
2857
2355
  .option('--json', 'Output as JSON')
2356
+ .option('--prune', 'Remove stale entries whose Discord channel no longer exists')
2858
2357
  .action(async (options) => {
2859
2358
  await initDatabase();
2860
2359
  const prisma = await getPrisma();
@@ -2871,31 +2370,67 @@ cli
2871
2370
  const rest = botRow ? createDiscordRest(botRow.token) : null;
2872
2371
  const enriched = await Promise.all(channels.map(async (ch) => {
2873
2372
  let channelName = '';
2373
+ let deleted = false;
2874
2374
  if (rest) {
2875
2375
  try {
2876
2376
  const data = (await rest.get(Routes.channel(ch.channel_id)));
2877
2377
  channelName = data.name || '';
2878
2378
  }
2879
- catch {
2880
- // Channel may have been deleted from Discord
2379
+ catch (error) {
2380
+ // Only mark as deleted for Unknown Channel (10003) or 404,
2381
+ // not transient errors like rate limits or 5xx
2382
+ const isUnknownChannel = error instanceof Error &&
2383
+ 'code' in error &&
2384
+ 'status' in error &&
2385
+ (error.code === 10003 ||
2386
+ error.status === 404);
2387
+ deleted = isUnknownChannel;
2881
2388
  }
2882
2389
  }
2883
- return { ...ch, channelName };
2390
+ return { ...ch, channelName, deleted };
2884
2391
  }));
2392
+ // Prune stale entries if requested
2393
+ if (options.prune) {
2394
+ const stale = enriched.filter((ch) => {
2395
+ return ch.deleted;
2396
+ });
2397
+ if (stale.length === 0) {
2398
+ cliLogger.log('No stale channels to prune');
2399
+ }
2400
+ else {
2401
+ for (const ch of stale) {
2402
+ await deleteChannelDirectoryById(ch.channel_id);
2403
+ cliLogger.log(`Pruned stale channel ${ch.channel_id} (${path.basename(ch.directory)})`);
2404
+ }
2405
+ cliLogger.log(`Pruned ${stale.length} stale channel(s)`);
2406
+ }
2407
+ // Re-filter to only show live entries after pruning
2408
+ const live = enriched.filter((ch) => {
2409
+ return !ch.deleted;
2410
+ });
2411
+ if (live.length === 0) {
2412
+ cliLogger.log('No projects registered');
2413
+ process.exit(0);
2414
+ }
2415
+ enriched.length = 0;
2416
+ enriched.push(...live);
2417
+ }
2885
2418
  if (options.json) {
2886
2419
  const output = enriched.map((ch) => ({
2887
2420
  channel_id: ch.channel_id,
2888
2421
  channel_name: ch.channelName,
2889
2422
  directory: ch.directory,
2890
2423
  folder_name: path.basename(ch.directory),
2424
+ deleted: ch.deleted,
2891
2425
  }));
2892
2426
  console.log(JSON.stringify(output, null, 2));
2893
2427
  process.exit(0);
2894
2428
  }
2895
2429
  for (const ch of enriched) {
2896
2430
  const folderName = path.basename(ch.directory);
2431
+ const deletedTag = ch.deleted ? ' (deleted from Discord)' : '';
2897
2432
  const channelLabel = ch.channelName ? `#${ch.channelName}` : ch.channel_id;
2898
- console.log(`\n${channelLabel}`);
2433
+ console.log(`\n${channelLabel}${deletedTag}`);
2899
2434
  console.log(` Folder: ${folderName}`);
2900
2435
  console.log(` Directory: ${ch.directory}`);
2901
2436
  console.log(` Channel ID: ${ch.channel_id}`);