@brianli/kimaki 0.4.72-brianli.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 (328) hide show
  1. package/bin.js +2 -0
  2. package/dist/ai-tool-to-genai.js +233 -0
  3. package/dist/ai-tool-to-genai.test.js +267 -0
  4. package/dist/ai-tool.js +6 -0
  5. package/dist/bin.js +87 -0
  6. package/dist/bot-token.js +121 -0
  7. package/dist/bot-token.test.js +134 -0
  8. package/dist/channel-management.js +101 -0
  9. package/dist/cli-parsing.test.js +89 -0
  10. package/dist/cli.js +2529 -0
  11. package/dist/commands/abort.js +82 -0
  12. package/dist/commands/action-buttons.js +257 -0
  13. package/dist/commands/add-project.js +114 -0
  14. package/dist/commands/agent.js +291 -0
  15. package/dist/commands/ask-question.js +223 -0
  16. package/dist/commands/compact.js +120 -0
  17. package/dist/commands/context-usage.js +140 -0
  18. package/dist/commands/create-new-project.js +118 -0
  19. package/dist/commands/diff.js +128 -0
  20. package/dist/commands/file-upload.js +275 -0
  21. package/dist/commands/fork.js +217 -0
  22. package/dist/commands/gemini-apikey.js +70 -0
  23. package/dist/commands/login.js +490 -0
  24. package/dist/commands/mention-mode.js +51 -0
  25. package/dist/commands/merge-worktree.js +124 -0
  26. package/dist/commands/model.js +694 -0
  27. package/dist/commands/permissions.js +163 -0
  28. package/dist/commands/queue.js +217 -0
  29. package/dist/commands/remove-project.js +115 -0
  30. package/dist/commands/restart-opencode-server.js +116 -0
  31. package/dist/commands/resume.js +159 -0
  32. package/dist/commands/run-command.js +79 -0
  33. package/dist/commands/session-id.js +78 -0
  34. package/dist/commands/session.js +192 -0
  35. package/dist/commands/share.js +80 -0
  36. package/dist/commands/types.js +2 -0
  37. package/dist/commands/undo-redo.js +159 -0
  38. package/dist/commands/unset-model.js +152 -0
  39. package/dist/commands/upgrade.js +42 -0
  40. package/dist/commands/user-command.js +148 -0
  41. package/dist/commands/verbosity.js +60 -0
  42. package/dist/commands/worktree-settings.js +50 -0
  43. package/dist/commands/worktree.js +299 -0
  44. package/dist/condense-memory.js +33 -0
  45. package/dist/config.js +110 -0
  46. package/dist/database.js +1050 -0
  47. package/dist/db.js +159 -0
  48. package/dist/db.test.js +49 -0
  49. package/dist/discord-api.js +28 -0
  50. package/dist/discord-auth.js +231 -0
  51. package/dist/discord-auth.test.js +80 -0
  52. package/dist/discord-bot.js +997 -0
  53. package/dist/discord-utils.js +560 -0
  54. package/dist/discord-utils.test.js +115 -0
  55. package/dist/errors.js +167 -0
  56. package/dist/escape-backticks.test.js +429 -0
  57. package/dist/format-tables.js +122 -0
  58. package/dist/format-tables.test.js +199 -0
  59. package/dist/forum-sync/config.js +79 -0
  60. package/dist/forum-sync/discord-operations.js +154 -0
  61. package/dist/forum-sync/index.js +5 -0
  62. package/dist/forum-sync/markdown.js +117 -0
  63. package/dist/forum-sync/sync-to-discord.js +417 -0
  64. package/dist/forum-sync/sync-to-files.js +190 -0
  65. package/dist/forum-sync/types.js +53 -0
  66. package/dist/forum-sync/watchers.js +307 -0
  67. package/dist/gateway-consumer.js +232 -0
  68. package/dist/gateway-consumer.test.js +18 -0
  69. package/dist/genai-worker-wrapper.js +111 -0
  70. package/dist/genai-worker.js +311 -0
  71. package/dist/genai.js +232 -0
  72. package/dist/generated/browser.js +17 -0
  73. package/dist/generated/client.js +35 -0
  74. package/dist/generated/commonInputTypes.js +10 -0
  75. package/dist/generated/enums.js +30 -0
  76. package/dist/generated/internal/class.js +41 -0
  77. package/dist/generated/internal/prismaNamespace.js +239 -0
  78. package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
  79. package/dist/generated/models/bot_api_keys.js +1 -0
  80. package/dist/generated/models/bot_tokens.js +1 -0
  81. package/dist/generated/models/channel_agents.js +1 -0
  82. package/dist/generated/models/channel_directories.js +1 -0
  83. package/dist/generated/models/channel_mention_mode.js +1 -0
  84. package/dist/generated/models/channel_models.js +1 -0
  85. package/dist/generated/models/channel_verbosity.js +1 -0
  86. package/dist/generated/models/channel_worktrees.js +1 -0
  87. package/dist/generated/models/forum_sync_configs.js +1 -0
  88. package/dist/generated/models/global_models.js +1 -0
  89. package/dist/generated/models/ipc_requests.js +1 -0
  90. package/dist/generated/models/part_messages.js +1 -0
  91. package/dist/generated/models/scheduled_tasks.js +1 -0
  92. package/dist/generated/models/session_agents.js +1 -0
  93. package/dist/generated/models/session_models.js +1 -0
  94. package/dist/generated/models/session_start_sources.js +1 -0
  95. package/dist/generated/models/thread_sessions.js +1 -0
  96. package/dist/generated/models/thread_worktrees.js +1 -0
  97. package/dist/generated/models.js +1 -0
  98. package/dist/heap-monitor.js +95 -0
  99. package/dist/hrana-server.js +416 -0
  100. package/dist/hrana-server.test.js +368 -0
  101. package/dist/image-utils.js +112 -0
  102. package/dist/interaction-handler.js +327 -0
  103. package/dist/ipc-polling.js +251 -0
  104. package/dist/kimaki-digital-twin.e2e.test.js +165 -0
  105. package/dist/limit-heading-depth.js +25 -0
  106. package/dist/limit-heading-depth.test.js +105 -0
  107. package/dist/logger.js +160 -0
  108. package/dist/markdown.js +342 -0
  109. package/dist/markdown.test.js +253 -0
  110. package/dist/message-formatting.js +433 -0
  111. package/dist/message-formatting.test.js +73 -0
  112. package/dist/openai-realtime.js +228 -0
  113. package/dist/opencode-plugin-loading.e2e.test.js +91 -0
  114. package/dist/opencode-plugin.js +536 -0
  115. package/dist/opencode-plugin.test.js +98 -0
  116. package/dist/opencode.js +409 -0
  117. package/dist/privacy-sanitizer.js +105 -0
  118. package/dist/runtime-mode.js +51 -0
  119. package/dist/runtime-mode.test.js +115 -0
  120. package/dist/sentry.js +127 -0
  121. package/dist/session-handler/state.js +151 -0
  122. package/dist/session-handler.js +1874 -0
  123. package/dist/session-search.js +100 -0
  124. package/dist/session-search.test.js +40 -0
  125. package/dist/startup-service.js +153 -0
  126. package/dist/system-message.js +499 -0
  127. package/dist/task-runner.js +282 -0
  128. package/dist/task-schedule.js +191 -0
  129. package/dist/task-schedule.test.js +71 -0
  130. package/dist/thinking-utils.js +35 -0
  131. package/dist/thread-message-queue.e2e.test.js +781 -0
  132. package/dist/tools.js +359 -0
  133. package/dist/unnest-code-blocks.js +136 -0
  134. package/dist/unnest-code-blocks.test.js +641 -0
  135. package/dist/upgrade.js +114 -0
  136. package/dist/utils.js +109 -0
  137. package/dist/voice-handler.js +606 -0
  138. package/dist/voice.js +304 -0
  139. package/dist/voice.test.js +187 -0
  140. package/dist/wait-session.js +94 -0
  141. package/dist/worker-types.js +4 -0
  142. package/dist/worktree-utils.js +727 -0
  143. package/dist/xml.js +92 -0
  144. package/dist/xml.test.js +32 -0
  145. package/package.json +82 -0
  146. package/schema.prisma +246 -0
  147. package/skills/batch/SKILL.md +87 -0
  148. package/skills/critique/SKILL.md +129 -0
  149. package/skills/errore/SKILL.md +589 -0
  150. package/skills/goke/.prettierrc +5 -0
  151. package/skills/goke/CHANGELOG.md +40 -0
  152. package/skills/goke/LICENSE +21 -0
  153. package/skills/goke/README.md +666 -0
  154. package/skills/goke/SKILL.md +458 -0
  155. package/skills/goke/package.json +43 -0
  156. package/skills/goke/src/__test__/coerce.test.ts +411 -0
  157. package/skills/goke/src/__test__/index.test.ts +1798 -0
  158. package/skills/goke/src/__test__/types.test-d.ts +111 -0
  159. package/skills/goke/src/coerce.ts +547 -0
  160. package/skills/goke/src/goke.ts +1362 -0
  161. package/skills/goke/src/index.ts +16 -0
  162. package/skills/goke/src/mri.ts +164 -0
  163. package/skills/goke/tsconfig.json +15 -0
  164. package/skills/jitter/EDITOR.md +219 -0
  165. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  166. package/skills/jitter/SKILL.md +158 -0
  167. package/skills/jitter/jitter-clipboard.json +1042 -0
  168. package/skills/jitter/package.json +14 -0
  169. package/skills/jitter/tsconfig.json +15 -0
  170. package/skills/jitter/utils/actions.ts +212 -0
  171. package/skills/jitter/utils/export.ts +114 -0
  172. package/skills/jitter/utils/index.ts +141 -0
  173. package/skills/jitter/utils/snapshot.ts +154 -0
  174. package/skills/jitter/utils/traverse.ts +246 -0
  175. package/skills/jitter/utils/types.ts +279 -0
  176. package/skills/jitter/utils/wait.ts +133 -0
  177. package/skills/playwriter/SKILL.md +31 -0
  178. package/skills/security-review/SKILL.md +208 -0
  179. package/skills/simplify/SKILL.md +58 -0
  180. package/skills/termcast/SKILL.md +945 -0
  181. package/skills/tuistory/SKILL.md +250 -0
  182. package/skills/zustand-centralized-state/SKILL.md +582 -0
  183. package/src/__snapshots__/compact-session-context-no-system.md +35 -0
  184. package/src/__snapshots__/compact-session-context.md +41 -0
  185. package/src/__snapshots__/first-session-no-info.md +17 -0
  186. package/src/__snapshots__/first-session-with-info.md +23 -0
  187. package/src/__snapshots__/session-1.md +17 -0
  188. package/src/__snapshots__/session-2.md +5871 -0
  189. package/src/__snapshots__/session-3.md +17 -0
  190. package/src/__snapshots__/session-with-tools.md +5871 -0
  191. package/src/ai-tool-to-genai.test.ts +296 -0
  192. package/src/ai-tool-to-genai.ts +282 -0
  193. package/src/ai-tool.ts +39 -0
  194. package/src/bin.ts +108 -0
  195. package/src/bot-token.test.ts +171 -0
  196. package/src/bot-token.ts +159 -0
  197. package/src/channel-management.ts +172 -0
  198. package/src/cli-parsing.test.ts +132 -0
  199. package/src/cli.ts +3605 -0
  200. package/src/commands/abort.ts +112 -0
  201. package/src/commands/action-buttons.ts +376 -0
  202. package/src/commands/add-project.ts +152 -0
  203. package/src/commands/agent.ts +404 -0
  204. package/src/commands/ask-question.ts +330 -0
  205. package/src/commands/compact.ts +157 -0
  206. package/src/commands/context-usage.ts +199 -0
  207. package/src/commands/create-new-project.ts +179 -0
  208. package/src/commands/diff.ts +165 -0
  209. package/src/commands/file-upload.ts +389 -0
  210. package/src/commands/fork.ts +320 -0
  211. package/src/commands/gemini-apikey.ts +104 -0
  212. package/src/commands/login.ts +634 -0
  213. package/src/commands/mention-mode.ts +77 -0
  214. package/src/commands/merge-worktree.ts +177 -0
  215. package/src/commands/model.ts +961 -0
  216. package/src/commands/permissions.ts +261 -0
  217. package/src/commands/queue.ts +296 -0
  218. package/src/commands/remove-project.ts +155 -0
  219. package/src/commands/restart-opencode-server.ts +162 -0
  220. package/src/commands/resume.ts +242 -0
  221. package/src/commands/run-command.ts +123 -0
  222. package/src/commands/session-id.ts +109 -0
  223. package/src/commands/session.ts +250 -0
  224. package/src/commands/share.ts +106 -0
  225. package/src/commands/types.ts +25 -0
  226. package/src/commands/undo-redo.ts +221 -0
  227. package/src/commands/unset-model.ts +189 -0
  228. package/src/commands/upgrade.ts +52 -0
  229. package/src/commands/user-command.ts +193 -0
  230. package/src/commands/verbosity.ts +88 -0
  231. package/src/commands/worktree-settings.ts +79 -0
  232. package/src/commands/worktree.ts +431 -0
  233. package/src/condense-memory.ts +36 -0
  234. package/src/config.ts +148 -0
  235. package/src/database.ts +1530 -0
  236. package/src/db.test.ts +60 -0
  237. package/src/db.ts +190 -0
  238. package/src/discord-api.ts +35 -0
  239. package/src/discord-bot.ts +1316 -0
  240. package/src/discord-utils.test.ts +132 -0
  241. package/src/discord-utils.ts +767 -0
  242. package/src/errors.ts +213 -0
  243. package/src/escape-backticks.test.ts +469 -0
  244. package/src/format-tables.test.ts +223 -0
  245. package/src/format-tables.ts +145 -0
  246. package/src/forum-sync/config.ts +92 -0
  247. package/src/forum-sync/discord-operations.ts +241 -0
  248. package/src/forum-sync/index.ts +9 -0
  249. package/src/forum-sync/markdown.ts +176 -0
  250. package/src/forum-sync/sync-to-discord.ts +595 -0
  251. package/src/forum-sync/sync-to-files.ts +294 -0
  252. package/src/forum-sync/types.ts +175 -0
  253. package/src/forum-sync/watchers.ts +454 -0
  254. package/src/genai-worker-wrapper.ts +164 -0
  255. package/src/genai-worker.ts +386 -0
  256. package/src/genai.ts +321 -0
  257. package/src/generated/browser.ts +109 -0
  258. package/src/generated/client.ts +131 -0
  259. package/src/generated/commonInputTypes.ts +512 -0
  260. package/src/generated/enums.ts +46 -0
  261. package/src/generated/internal/class.ts +362 -0
  262. package/src/generated/internal/prismaNamespace.ts +2251 -0
  263. package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
  264. package/src/generated/models/bot_api_keys.ts +1288 -0
  265. package/src/generated/models/bot_tokens.ts +1577 -0
  266. package/src/generated/models/channel_agents.ts +1256 -0
  267. package/src/generated/models/channel_directories.ts +2104 -0
  268. package/src/generated/models/channel_mention_mode.ts +1300 -0
  269. package/src/generated/models/channel_models.ts +1288 -0
  270. package/src/generated/models/channel_verbosity.ts +1224 -0
  271. package/src/generated/models/channel_worktrees.ts +1308 -0
  272. package/src/generated/models/forum_sync_configs.ts +1452 -0
  273. package/src/generated/models/global_models.ts +1288 -0
  274. package/src/generated/models/ipc_requests.ts +1485 -0
  275. package/src/generated/models/part_messages.ts +1302 -0
  276. package/src/generated/models/scheduled_tasks.ts +2320 -0
  277. package/src/generated/models/session_agents.ts +1086 -0
  278. package/src/generated/models/session_models.ts +1114 -0
  279. package/src/generated/models/session_start_sources.ts +1408 -0
  280. package/src/generated/models/thread_sessions.ts +1599 -0
  281. package/src/generated/models/thread_worktrees.ts +1352 -0
  282. package/src/generated/models.ts +29 -0
  283. package/src/heap-monitor.ts +121 -0
  284. package/src/hrana-server.test.ts +428 -0
  285. package/src/hrana-server.ts +547 -0
  286. package/src/image-utils.ts +149 -0
  287. package/src/interaction-handler.ts +461 -0
  288. package/src/ipc-polling.ts +325 -0
  289. package/src/kimaki-digital-twin.e2e.test.ts +201 -0
  290. package/src/limit-heading-depth.test.ts +116 -0
  291. package/src/limit-heading-depth.ts +26 -0
  292. package/src/logger.ts +203 -0
  293. package/src/markdown.test.ts +360 -0
  294. package/src/markdown.ts +410 -0
  295. package/src/message-formatting.test.ts +81 -0
  296. package/src/message-formatting.ts +549 -0
  297. package/src/openai-realtime.ts +362 -0
  298. package/src/opencode-plugin-loading.e2e.test.ts +112 -0
  299. package/src/opencode-plugin.test.ts +108 -0
  300. package/src/opencode-plugin.ts +652 -0
  301. package/src/opencode.ts +554 -0
  302. package/src/privacy-sanitizer.ts +142 -0
  303. package/src/schema.sql +158 -0
  304. package/src/sentry.ts +137 -0
  305. package/src/session-handler/state.ts +232 -0
  306. package/src/session-handler.ts +2668 -0
  307. package/src/session-search.test.ts +50 -0
  308. package/src/session-search.ts +148 -0
  309. package/src/startup-service.ts +200 -0
  310. package/src/system-message.ts +568 -0
  311. package/src/task-runner.ts +425 -0
  312. package/src/task-schedule.test.ts +84 -0
  313. package/src/task-schedule.ts +287 -0
  314. package/src/thinking-utils.ts +61 -0
  315. package/src/thread-message-queue.e2e.test.ts +997 -0
  316. package/src/tools.ts +432 -0
  317. package/src/unnest-code-blocks.test.ts +679 -0
  318. package/src/unnest-code-blocks.ts +168 -0
  319. package/src/upgrade.ts +127 -0
  320. package/src/utils.ts +145 -0
  321. package/src/voice-handler.ts +852 -0
  322. package/src/voice.test.ts +219 -0
  323. package/src/voice.ts +444 -0
  324. package/src/wait-session.ts +147 -0
  325. package/src/worker-types.ts +64 -0
  326. package/src/worktree-utils.ts +988 -0
  327. package/src/xml.test.ts +38 -0
  328. package/src/xml.ts +121 -0
@@ -0,0 +1,189 @@
1
+ // /unset-model-override command - Remove model overrides and use default instead.
2
+
3
+ import {
4
+ ChatInputCommandInteraction,
5
+ ChannelType,
6
+ type ThreadChannel,
7
+ type TextChannel,
8
+ MessageFlags,
9
+ } from 'discord.js'
10
+ import {
11
+ getChannelModel,
12
+ getSessionModel,
13
+ getThreadSession,
14
+ clearSessionModel,
15
+ } from '../database.js'
16
+ import { getPrisma } from '../db.js'
17
+ import { initializeOpencodeForDirectory } from '../opencode.js'
18
+ import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
19
+ import { abortAndRetrySession } from '../session-handler.js'
20
+ import { getCurrentModelInfo } from './model.js'
21
+ import { createLogger, LogPrefix } from '../logger.js'
22
+
23
+ const unsetModelLogger = createLogger(LogPrefix.MODEL)
24
+
25
+ function formatModelSource(type: string, agentName?: string): string {
26
+ switch (type) {
27
+ case 'session':
28
+ return 'session override'
29
+ case 'agent':
30
+ return `agent "${agentName}"`
31
+ case 'channel':
32
+ return 'channel override'
33
+ case 'global':
34
+ return 'global default'
35
+ case 'opencode-config':
36
+ case 'opencode-recent':
37
+ case 'opencode-provider-default':
38
+ return 'opencode default'
39
+ default:
40
+ return 'none'
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Handle the /unset-model-override slash command.
46
+ * In thread: clears session override if exists, otherwise channel override.
47
+ * In channel: clears channel override.
48
+ */
49
+ export async function handleUnsetModelCommand({
50
+ interaction,
51
+ appId,
52
+ }: {
53
+ interaction: ChatInputCommandInteraction
54
+ appId: string
55
+ }): Promise<void> {
56
+ unsetModelLogger.log('[UNSET-MODEL] handleUnsetModelCommand called')
57
+
58
+ await interaction.deferReply({ flags: MessageFlags.Ephemeral })
59
+
60
+ const channel = interaction.channel
61
+
62
+ if (!channel) {
63
+ await interaction.editReply({
64
+ content: 'This command can only be used in a channel',
65
+ })
66
+ return
67
+ }
68
+
69
+ const isThread = [
70
+ ChannelType.PublicThread,
71
+ ChannelType.PrivateThread,
72
+ ChannelType.AnnouncementThread,
73
+ ].includes(channel.type)
74
+
75
+ let projectDirectory: string | undefined
76
+ let channelAppId: string | undefined
77
+ let targetChannelId: string
78
+ let sessionId: string | undefined
79
+
80
+ if (isThread) {
81
+ const thread = channel as ThreadChannel
82
+ const textChannel = await resolveTextChannel(thread)
83
+ const metadata = await getKimakiMetadata(textChannel)
84
+ projectDirectory = metadata.projectDirectory
85
+ channelAppId = metadata.channelAppId
86
+ targetChannelId = textChannel?.id || channel.id
87
+ sessionId = await getThreadSession(thread.id)
88
+ } else if (channel.type === ChannelType.GuildText) {
89
+ const textChannel = channel as TextChannel
90
+ const metadata = await getKimakiMetadata(textChannel)
91
+ projectDirectory = metadata.projectDirectory
92
+ channelAppId = metadata.channelAppId
93
+ targetChannelId = channel.id
94
+ } else {
95
+ await interaction.editReply({
96
+ content: 'This command can only be used in text channels or threads',
97
+ })
98
+ return
99
+ }
100
+
101
+ if (channelAppId && channelAppId !== appId) {
102
+ await interaction.editReply({
103
+ content: 'This channel is not configured for this bot',
104
+ })
105
+ return
106
+ }
107
+
108
+ if (!projectDirectory) {
109
+ await interaction.editReply({
110
+ content: 'This channel is not configured with a project directory',
111
+ })
112
+ return
113
+ }
114
+
115
+ const effectiveAppId = channelAppId || appId
116
+
117
+ // Check what overrides exist
118
+ const [sessionPref, channelPref] = await Promise.all([
119
+ sessionId ? getSessionModel(sessionId) : Promise.resolve(undefined),
120
+ getChannelModel(targetChannelId),
121
+ ])
122
+
123
+ let clearedType: 'session' | 'channel' | null = null
124
+ let clearedModel: string | undefined
125
+
126
+ if (isThread && sessionId && sessionPref) {
127
+ // In thread with session override: clear session
128
+ await clearSessionModel(sessionId)
129
+ clearedType = 'session'
130
+ clearedModel = sessionPref.modelId
131
+ unsetModelLogger.log(`[UNSET-MODEL] Cleared session model for ${sessionId}`)
132
+ } else if (channelPref) {
133
+ // Clear channel override
134
+ const prisma = await getPrisma()
135
+ await prisma.channel_models.deleteMany({
136
+ where: { channel_id: targetChannelId },
137
+ })
138
+ clearedType = 'channel'
139
+ clearedModel = channelPref.modelId
140
+ unsetModelLogger.log(
141
+ `[UNSET-MODEL] Cleared channel model for ${targetChannelId}`,
142
+ )
143
+ } else {
144
+ await interaction.editReply({
145
+ content: 'No model override to clear.',
146
+ })
147
+ return
148
+ }
149
+
150
+ // Get the new model that will be used
151
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
152
+ let newModelText = 'unknown'
153
+
154
+ if (!(getClient instanceof Error)) {
155
+ const newModelInfo = await getCurrentModelInfo({
156
+ sessionId,
157
+ channelId: targetChannelId,
158
+ appId: effectiveAppId,
159
+ getClient,
160
+ })
161
+
162
+ newModelText =
163
+ newModelInfo.type === 'none'
164
+ ? 'none'
165
+ : `\`${newModelInfo.model}\` (${formatModelSource(newModelInfo.type, 'agentName' in newModelInfo ? newModelInfo.agentName : undefined)})`
166
+ }
167
+
168
+ // Check if there's a running request and abort+retry with new model (only for session changes in threads)
169
+ let retried = false
170
+ if (isThread && clearedType === 'session' && sessionId) {
171
+ const thread = channel as ThreadChannel
172
+ retried = await abortAndRetrySession({
173
+ sessionId,
174
+ thread,
175
+ projectDirectory,
176
+ appId: effectiveAppId,
177
+ channelId: targetChannelId,
178
+ })
179
+ }
180
+
181
+ const clearedTypeText = clearedType === 'session' ? 'Session' : 'Channel'
182
+ const retriedText = retried
183
+ ? '\n_Retrying current request with new model..._'
184
+ : ''
185
+
186
+ await interaction.editReply({
187
+ content: `${clearedTypeText} model override removed.\n**Was:** \`${clearedModel}\`\n**Now using:** ${newModelText}${retriedText}`,
188
+ })
189
+ }
@@ -0,0 +1,52 @@
1
+ // /upgrade-and-restart command - Upgrade kimaki to the latest version and restart the bot.
2
+ // Checks npm for a newer version, installs it globally, then spawns a new kimaki process.
3
+ // The new process kills the old one on startup (kimaki's single-instance lock).
4
+
5
+ import type { CommandContext } from './types.js'
6
+ import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
7
+ import { createLogger, LogPrefix } from '../logger.js'
8
+ import { getCurrentVersion, upgrade } from '../upgrade.js'
9
+ import { spawn } from 'node:child_process'
10
+
11
+ const logger = createLogger(LogPrefix.CLI)
12
+
13
+ export async function handleUpgradeAndRestartCommand({
14
+ command,
15
+ }: CommandContext): Promise<void> {
16
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
17
+
18
+ logger.log('[UPGRADE] /upgrade-and-restart triggered')
19
+
20
+ try {
21
+ const currentVersion = getCurrentVersion()
22
+ const newVersion = await upgrade()
23
+
24
+ if (!newVersion) {
25
+ await command.editReply({
26
+ content: `Already on latest version: **v${currentVersion}**`,
27
+ })
28
+ return
29
+ }
30
+
31
+ await command.editReply({
32
+ content: `Upgraded kimaki **v${currentVersion}** -> **v${newVersion}**. Restarting bot...`,
33
+ })
34
+
35
+ // Spawning bare `kimaki` works even if the user originally ran via npx/bunx:
36
+ // `npm i -g kimaki@latest` creates a global bin link, and npx resolves
37
+ // local -> global -> cache -> registry, so it prefers the global install.
38
+ // bunx shares the same global cache, so it also picks up the new version.
39
+ const child = spawn('kimaki', process.argv.slice(2), {
40
+ shell: true,
41
+ stdio: 'ignore',
42
+ detached: true,
43
+ })
44
+ child.unref()
45
+ logger.debug('Started new background kimaki')
46
+ } catch (error) {
47
+ logger.error('[UPGRADE] Failed:', error)
48
+ await command.editReply({
49
+ content: `Upgrade failed: ${error instanceof Error ? error.message : String(error)}`,
50
+ })
51
+ }
52
+ }
@@ -0,0 +1,193 @@
1
+ // User-defined OpenCode command handler.
2
+ // Handles slash commands that map to user-configured commands in opencode.json.
3
+
4
+ import type { CommandContext, CommandHandler } from './types.js'
5
+ import {
6
+ ChannelType,
7
+ MessageFlags,
8
+ type TextChannel,
9
+ type ThreadChannel,
10
+ } from 'discord.js'
11
+ import { handleOpencodeSession } from '../session-handler.js'
12
+ import { sendThreadMessage, SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
13
+ import { createLogger, LogPrefix } from '../logger.js'
14
+ import { getChannelDirectory, getThreadSession } from '../database.js'
15
+ import { registeredUserCommands } from '../config.js'
16
+ import fs from 'node:fs'
17
+
18
+ const userCommandLogger = createLogger(LogPrefix.USER_CMD)
19
+
20
+ export const handleUserCommand: CommandHandler = async ({
21
+ command,
22
+ appId,
23
+ }: CommandContext) => {
24
+ const discordCommandName = command.commandName
25
+ // Look up the original OpenCode command name from the mapping populated at registration.
26
+ // The sanitized Discord name is lossy (e.g. foo:bar → foo-bar), so stripping -cmd
27
+ // would give the wrong name for commands with special characters.
28
+ const sanitizedBase = discordCommandName.replace(/-cmd$/, '')
29
+ const registered = registeredUserCommands.find(
30
+ (c) => c.discordName === sanitizedBase,
31
+ )
32
+ const commandName = registered?.name || sanitizedBase
33
+ const args = command.options.getString('arguments') || ''
34
+
35
+ userCommandLogger.log(
36
+ `Executing /${commandName} (from /${discordCommandName}) argsLength=${args.length}`,
37
+ )
38
+
39
+ const channel = command.channel
40
+
41
+ userCommandLogger.log(
42
+ `Channel info: type=${channel?.type}, id=${channel?.id}, isNull=${channel === null}`,
43
+ )
44
+
45
+ const isThread =
46
+ channel &&
47
+ [
48
+ ChannelType.PublicThread,
49
+ ChannelType.PrivateThread,
50
+ ChannelType.AnnouncementThread,
51
+ ].includes(channel.type)
52
+
53
+ const isTextChannel = channel?.type === ChannelType.GuildText
54
+
55
+ if (!channel || (!isTextChannel && !isThread)) {
56
+ await command.reply({
57
+ content: 'This command can only be used in text channels or threads',
58
+ flags: MessageFlags.Ephemeral,
59
+ })
60
+ return
61
+ }
62
+
63
+ let projectDirectory: string | undefined
64
+ let channelAppId: string | undefined
65
+ let textChannel: TextChannel | null = null
66
+ let thread: ThreadChannel | null = null
67
+
68
+ if (isThread) {
69
+ // Running in an existing thread - get project directory from parent channel
70
+ thread = channel as ThreadChannel
71
+ textChannel = thread.parent as TextChannel | null
72
+
73
+ // Verify this thread has an existing session
74
+ const sessionId = await getThreadSession(thread.id)
75
+
76
+ if (!sessionId) {
77
+ await command.reply({
78
+ content:
79
+ 'This thread does not have an active session. Use this command in a project channel to create a new thread.',
80
+ flags: MessageFlags.Ephemeral,
81
+ })
82
+ return
83
+ }
84
+
85
+ if (textChannel) {
86
+ const channelConfig = await getChannelDirectory(textChannel.id)
87
+ projectDirectory = channelConfig?.directory
88
+ channelAppId = channelConfig?.appId || undefined
89
+ }
90
+ } else {
91
+ // Running in a text channel - will create a new thread
92
+ textChannel = channel as TextChannel
93
+
94
+ const channelConfig = await getChannelDirectory(textChannel.id)
95
+ projectDirectory = channelConfig?.directory
96
+ channelAppId = channelConfig?.appId || undefined
97
+ }
98
+
99
+ if (channelAppId && channelAppId !== appId) {
100
+ await command.reply({
101
+ content: 'This channel is not configured for this bot',
102
+ flags: MessageFlags.Ephemeral,
103
+ })
104
+ return
105
+ }
106
+
107
+ if (!projectDirectory) {
108
+ await command.reply({
109
+ content: 'This channel is not configured with a project directory',
110
+ flags: MessageFlags.Ephemeral,
111
+ })
112
+ return
113
+ }
114
+
115
+ if (!fs.existsSync(projectDirectory)) {
116
+ await command.reply({
117
+ content: `Directory does not exist: ${projectDirectory}`,
118
+ flags: MessageFlags.Ephemeral,
119
+ })
120
+ return
121
+ }
122
+
123
+ await command.deferReply({ ephemeral: false })
124
+
125
+ try {
126
+ // Use the dedicated session.command API instead of formatting as text prompt
127
+ const commandPayload = { name: commandName, arguments: args }
128
+
129
+ if (isThread && thread) {
130
+ // Running in existing thread - just send the command
131
+ await command.editReply(`Running /${commandName}...`)
132
+
133
+ await handleOpencodeSession({
134
+ prompt: '', // Not used when command is set
135
+ thread,
136
+ projectDirectory,
137
+ channelId: textChannel?.id,
138
+ command: commandPayload,
139
+ appId,
140
+ })
141
+ } else if (textChannel) {
142
+ // Running in text channel - create a new thread
143
+ const starterMessage = await textChannel.send({
144
+ content: `**/${commandName}**`,
145
+ flags: SILENT_MESSAGE_FLAGS,
146
+ })
147
+
148
+ const threadName = `/${commandName}`
149
+ const newThread = await starterMessage.startThread({
150
+ name: threadName.slice(0, 100),
151
+ autoArchiveDuration: 1440,
152
+ reason: `OpenCode command: ${commandName}`,
153
+ })
154
+
155
+ // Add user to thread so it appears in their sidebar
156
+ await newThread.members.add(command.user.id)
157
+
158
+ if (args) {
159
+ const argsPreview =
160
+ args.length > 1800 ? `${args.slice(0, 1800)}\n... truncated` : args
161
+ await sendThreadMessage(newThread, `Args: ${argsPreview}`)
162
+ }
163
+
164
+ await command.editReply(
165
+ `Started /${commandName} in ${newThread.toString()}`,
166
+ )
167
+
168
+ await handleOpencodeSession({
169
+ prompt: '', // Not used when command is set
170
+ thread: newThread,
171
+ projectDirectory,
172
+ channelId: textChannel.id,
173
+ command: commandPayload,
174
+ appId,
175
+ })
176
+ }
177
+ } catch (error) {
178
+ userCommandLogger.error(`Error executing /${commandName}:`, error)
179
+
180
+ const errorMessage = error instanceof Error ? error.message : String(error)
181
+
182
+ if (command.deferred) {
183
+ await command.editReply({
184
+ content: `Failed to execute /${commandName}: ${errorMessage}`,
185
+ })
186
+ } else {
187
+ await command.reply({
188
+ content: `Failed to execute /${commandName}: ${errorMessage}`,
189
+ flags: MessageFlags.Ephemeral,
190
+ })
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,88 @@
1
+ // /verbosity command.
2
+ // Sets the output verbosity level for sessions in a channel.
3
+ // 'text-and-essential-tools' (default): shows text and essential tools (edits, custom MCP tools)
4
+ // 'tools-and-text': shows all output including tool executions
5
+ // 'text-only': only shows text responses (⬥ diamond parts)
6
+
7
+ import {
8
+ ChatInputCommandInteraction,
9
+ MessageFlags,
10
+ ChannelType,
11
+ type TextChannel,
12
+ type ThreadChannel,
13
+ } from 'discord.js'
14
+ import {
15
+ getChannelVerbosity,
16
+ setChannelVerbosity,
17
+ type VerbosityLevel,
18
+ } from '../database.js'
19
+ import { createLogger, LogPrefix } from '../logger.js'
20
+
21
+ const verbosityLogger = createLogger(LogPrefix.VERBOSITY)
22
+
23
+ /**
24
+ * Handle the /verbosity slash command.
25
+ * Sets output verbosity for the channel (applies immediately, even mid-session).
26
+ */
27
+ export async function handleVerbosityCommand({
28
+ command,
29
+ appId,
30
+ }: {
31
+ command: ChatInputCommandInteraction
32
+ appId: string
33
+ }): Promise<void> {
34
+ verbosityLogger.log('[VERBOSITY] Command called')
35
+
36
+ const channel = command.channel
37
+ if (!channel) {
38
+ await command.reply({
39
+ content: 'Could not determine channel.',
40
+ flags: MessageFlags.Ephemeral,
41
+ })
42
+ return
43
+ }
44
+
45
+ // Get the parent channel ID (for threads, use parent; for text channels, use self)
46
+ const channelId = (() => {
47
+ if (channel.type === ChannelType.GuildText) {
48
+ return channel.id
49
+ }
50
+ if (
51
+ channel.type === ChannelType.PublicThread ||
52
+ channel.type === ChannelType.PrivateThread ||
53
+ channel.type === ChannelType.AnnouncementThread
54
+ ) {
55
+ return (channel as ThreadChannel).parentId || channel.id
56
+ }
57
+ return channel.id
58
+ })()
59
+
60
+ const level = command.options.getString('level', true) as VerbosityLevel
61
+ const currentLevel = await getChannelVerbosity(channelId)
62
+
63
+ if (currentLevel === level) {
64
+ await command.reply({
65
+ content: `Verbosity is already set to **${level}** for this channel.`,
66
+ flags: MessageFlags.Ephemeral,
67
+ })
68
+ return
69
+ }
70
+
71
+ await setChannelVerbosity(channelId, level)
72
+ verbosityLogger.log(`[VERBOSITY] Set channel ${channelId} to ${level}`)
73
+
74
+ const description = (() => {
75
+ if (level === 'text-only') {
76
+ return 'Only text responses will be shown. Tool executions, status messages, and thinking will be hidden.'
77
+ }
78
+ if (level === 'text-and-essential-tools') {
79
+ return 'Text responses and essential tools (edits, custom MCP tools) will be shown. Read, search, and navigation tools will be hidden.'
80
+ }
81
+ return 'All output will be shown, including tool executions and status messages.'
82
+ })()
83
+
84
+ await command.reply({
85
+ content: `Verbosity set to **${level}** for this channel.\n${description}\nThis is a per-channel setting and applies immediately, including any active sessions.`,
86
+ flags: MessageFlags.Ephemeral,
87
+ })
88
+ }
@@ -0,0 +1,79 @@
1
+ // /toggle-worktrees command.
2
+ // Allows per-channel opt-in for automatic worktree creation,
3
+ // as an alternative to the global --use-worktrees CLI flag.
4
+
5
+ import {
6
+ ChatInputCommandInteraction,
7
+ MessageFlags,
8
+ ChannelType,
9
+ type TextChannel,
10
+ } from 'discord.js'
11
+ import {
12
+ getChannelWorktreesEnabled,
13
+ setChannelWorktreesEnabled,
14
+ } from '../database.js'
15
+ import { getKimakiMetadata } from '../discord-utils.js'
16
+ import { createLogger, LogPrefix } from '../logger.js'
17
+
18
+ const worktreeSettingsLogger = createLogger(LogPrefix.WORKTREE)
19
+
20
+ /**
21
+ * Handle the /toggle-worktrees slash command.
22
+ * Toggles automatic worktree creation for new sessions in this channel.
23
+ */
24
+ export async function handleToggleWorktreesCommand({
25
+ command,
26
+ appId,
27
+ }: {
28
+ command: ChatInputCommandInteraction
29
+ appId: string
30
+ }): Promise<void> {
31
+ worktreeSettingsLogger.log('[TOGGLE_WORKTREES] Command called')
32
+
33
+ const channel = command.channel
34
+
35
+ if (!channel || channel.type !== ChannelType.GuildText) {
36
+ await command.reply({
37
+ content: 'This command can only be used in text channels (not threads).',
38
+ flags: MessageFlags.Ephemeral,
39
+ })
40
+ return
41
+ }
42
+
43
+ const textChannel = channel as TextChannel
44
+ const metadata = await getKimakiMetadata(textChannel)
45
+
46
+ if (metadata.channelAppId && metadata.channelAppId !== appId) {
47
+ await command.reply({
48
+ content: 'This channel is configured for a different bot.',
49
+ flags: MessageFlags.Ephemeral,
50
+ })
51
+ return
52
+ }
53
+
54
+ if (!metadata.projectDirectory) {
55
+ await command.reply({
56
+ content:
57
+ 'This channel is not configured with a project directory.\nUse `/add-project` to set up this channel.',
58
+ flags: MessageFlags.Ephemeral,
59
+ })
60
+ return
61
+ }
62
+
63
+ const wasEnabled = await getChannelWorktreesEnabled(textChannel.id)
64
+ const nextEnabled = !wasEnabled
65
+ await setChannelWorktreesEnabled(textChannel.id, nextEnabled)
66
+
67
+ const nextLabel = nextEnabled ? 'enabled' : 'disabled'
68
+
69
+ worktreeSettingsLogger.log(
70
+ `[TOGGLE_WORKTREES] ${nextLabel.toUpperCase()} for channel ${textChannel.id}`,
71
+ )
72
+
73
+ await command.reply({
74
+ content: nextEnabled
75
+ ? `Worktrees **enabled** for this channel.\n\nNew sessions started from messages in **#${textChannel.name}** will now automatically create git worktrees.\n\nNew setting for **#${textChannel.name}**: **enabled**.`
76
+ : `Worktrees **disabled** for this channel.\n\nNew sessions started from messages in **#${textChannel.name}** will use the main project directory.\n\nNew setting for **#${textChannel.name}**: **disabled**.`,
77
+ flags: MessageFlags.Ephemeral,
78
+ })
79
+ }