@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,179 @@
1
+ // /create-new-project command - Create a new project folder, initialize git, and start a session.
2
+ // Also exports createNewProject() for reuse during onboarding (welcome channel creation).
3
+
4
+ import { ChannelType, type Guild, type TextChannel } from 'discord.js'
5
+ import fs from 'node:fs'
6
+ import path from 'node:path'
7
+ import { execSync } from 'node:child_process'
8
+ import type { CommandContext } from './types.js'
9
+ import { getProjectsDir } from '../config.js'
10
+ import { createProjectChannels } from '../channel-management.js'
11
+ import { handleOpencodeSession } from '../session-handler.js'
12
+ import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
13
+ import { createLogger, LogPrefix } from '../logger.js'
14
+
15
+ const logger = createLogger(LogPrefix.CREATE_PROJECT)
16
+
17
+ /**
18
+ * Core project creation logic: creates directory, inits git, creates Discord channels.
19
+ * Reused by the slash command handler and by onboarding (welcome channel).
20
+ * Returns null if the project directory already exists.
21
+ */
22
+ export async function createNewProject({
23
+ guild,
24
+ projectName,
25
+ appId,
26
+ botName,
27
+ }: {
28
+ guild: Guild
29
+ projectName: string
30
+ appId: string
31
+ botName?: string
32
+ }): Promise<{
33
+ textChannelId: string
34
+ voiceChannelId: string | null
35
+ channelName: string
36
+ projectDirectory: string
37
+ sanitizedName: string
38
+ } | null> {
39
+ const sanitizedName = projectName
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9-]/g, '-')
42
+ .replace(/-+/g, '-')
43
+ .replace(/^-|-$/g, '')
44
+ .slice(0, 100)
45
+
46
+ if (!sanitizedName) {
47
+ return null
48
+ }
49
+
50
+ const projectsDir = getProjectsDir()
51
+ const projectDirectory = path.join(projectsDir, sanitizedName)
52
+
53
+ if (!fs.existsSync(projectsDir)) {
54
+ fs.mkdirSync(projectsDir, { recursive: true })
55
+ logger.log(`Created projects directory: ${projectsDir}`)
56
+ }
57
+
58
+ if (fs.existsSync(projectDirectory)) {
59
+ return null
60
+ }
61
+
62
+ fs.mkdirSync(projectDirectory, { recursive: true })
63
+ logger.log(`Created project directory: ${projectDirectory}`)
64
+
65
+ execSync('git init', { cwd: projectDirectory, stdio: 'pipe' })
66
+ logger.log(`Initialized git in: ${projectDirectory}`)
67
+
68
+ const { textChannelId, voiceChannelId, channelName } =
69
+ await createProjectChannels({
70
+ guild,
71
+ projectDirectory,
72
+ appId,
73
+ botName,
74
+ })
75
+
76
+ return {
77
+ textChannelId,
78
+ voiceChannelId,
79
+ channelName,
80
+ projectDirectory,
81
+ sanitizedName,
82
+ }
83
+ }
84
+
85
+ export async function handleCreateNewProjectCommand({
86
+ command,
87
+ appId,
88
+ }: CommandContext): Promise<void> {
89
+ await command.deferReply({ ephemeral: false })
90
+
91
+ const projectName = command.options.getString('name', true)
92
+ const guild = command.guild
93
+ const channel = command.channel
94
+
95
+ if (!guild) {
96
+ await command.editReply('This command can only be used in a guild')
97
+ return
98
+ }
99
+
100
+ if (!channel || channel.type !== ChannelType.GuildText) {
101
+ await command.editReply('This command can only be used in a text channel')
102
+ return
103
+ }
104
+
105
+ try {
106
+ const result = await createNewProject({
107
+ guild,
108
+ projectName,
109
+ appId,
110
+ botName: command.client.user?.username,
111
+ })
112
+
113
+ if (!result) {
114
+ const sanitizedName = projectName
115
+ .toLowerCase()
116
+ .replace(/[^a-z0-9-]/g, '-')
117
+ .replace(/-+/g, '-')
118
+ .replace(/^-|-$/g, '')
119
+ .slice(0, 100)
120
+
121
+ if (!sanitizedName) {
122
+ await command.editReply('Invalid project name')
123
+ return
124
+ }
125
+
126
+ const projectDirectory = path.join(getProjectsDir(), sanitizedName)
127
+ await command.editReply(
128
+ `Project directory already exists: ${projectDirectory}`,
129
+ )
130
+ return
131
+ }
132
+
133
+ const {
134
+ textChannelId,
135
+ voiceChannelId,
136
+ channelName,
137
+ projectDirectory,
138
+ sanitizedName,
139
+ } = result
140
+ const textChannel = (await guild.channels.fetch(
141
+ textChannelId,
142
+ )) as TextChannel
143
+
144
+ const voiceInfo = voiceChannelId ? `\n🔊 Voice: <#${voiceChannelId}>` : ''
145
+ await command.editReply(
146
+ `✅ Created new project **${sanitizedName}**\n📁 Directory: \`${projectDirectory}\`\n📝 Text: <#${textChannelId}>${voiceInfo}\n_Starting session..._`,
147
+ )
148
+
149
+ const starterMessage = await textChannel.send({
150
+ content: `🚀 **New project initialized**\n📁 \`${projectDirectory}\``,
151
+ flags: SILENT_MESSAGE_FLAGS,
152
+ })
153
+
154
+ const thread = await starterMessage.startThread({
155
+ name: `Init: ${sanitizedName}`,
156
+ autoArchiveDuration: 1440,
157
+ reason: 'New project session',
158
+ })
159
+
160
+ // Add user to thread so it appears in their sidebar
161
+ await thread.members.add(command.user.id)
162
+
163
+ await handleOpencodeSession({
164
+ prompt:
165
+ 'The project was just initialized. Say hi and ask what the user wants to build.',
166
+ thread,
167
+ projectDirectory,
168
+ channelId: textChannel.id,
169
+ appId,
170
+ })
171
+
172
+ logger.log(`Created new project ${channelName} at ${projectDirectory}`)
173
+ } catch (error) {
174
+ logger.error('[CREATE-NEW-PROJECT] Error:', error)
175
+ await command.editReply(
176
+ `Failed to create new project: ${error instanceof Error ? error.message : 'Unknown error'}`,
177
+ )
178
+ }
179
+ }
@@ -0,0 +1,165 @@
1
+ // /diff command - Show git diff as a shareable URL.
2
+
3
+ import {
4
+ ChannelType,
5
+ EmbedBuilder,
6
+ MessageFlags,
7
+ type TextChannel,
8
+ type ThreadChannel,
9
+ } from 'discord.js'
10
+ import path from 'node:path'
11
+ import type { CommandContext } from './types.js'
12
+ import {
13
+ resolveWorkingDirectory,
14
+ SILENT_MESSAGE_FLAGS,
15
+ } from '../discord-utils.js'
16
+ import { createLogger, LogPrefix } from '../logger.js'
17
+ import { execAsync } from '../worktree-utils.js'
18
+
19
+ const logger = createLogger(LogPrefix.DIFF)
20
+
21
+ export async function handleDiffCommand({
22
+ command,
23
+ }: CommandContext): Promise<void> {
24
+ const channel = command.channel
25
+
26
+ if (!channel) {
27
+ await command.reply({
28
+ content: 'This command can only be used in a channel',
29
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
30
+ })
31
+ return
32
+ }
33
+
34
+ const isThread = [
35
+ ChannelType.PublicThread,
36
+ ChannelType.PrivateThread,
37
+ ChannelType.AnnouncementThread,
38
+ ].includes(channel.type)
39
+
40
+ const isTextChannel = channel.type === ChannelType.GuildText
41
+
42
+ if (!isThread && !isTextChannel) {
43
+ await command.reply({
44
+ content: 'This command can only be used in a text channel or thread',
45
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
46
+ })
47
+ return
48
+ }
49
+
50
+ const resolved = await resolveWorkingDirectory({
51
+ channel: channel as TextChannel | ThreadChannel,
52
+ })
53
+
54
+ if (!resolved) {
55
+ await command.reply({
56
+ content: 'Could not determine project directory for this channel',
57
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
58
+ })
59
+ return
60
+ }
61
+
62
+ const { workingDirectory } = resolved
63
+
64
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
65
+
66
+ try {
67
+ const projectName = path.basename(workingDirectory)
68
+ const title = `${projectName}: Discord /diff`
69
+ const { stdout, stderr } = await execAsync(
70
+ `bunx critique --web "${title}" --json`,
71
+ {
72
+ cwd: workingDirectory,
73
+ timeout: 30000,
74
+ },
75
+ )
76
+
77
+ // critique --json outputs JSON on the last line: {"url":"...","id":"..."} or {"error":"..."}
78
+ const output = stdout || stderr
79
+ const lines = output.trim().split('\n')
80
+ const jsonLine = lines[lines.length - 1]
81
+ if (!jsonLine) {
82
+ await command.editReply({
83
+ content: 'No changes to show',
84
+ })
85
+ return
86
+ }
87
+
88
+ let result: { url?: string; id?: string; error?: string }
89
+ try {
90
+ result = JSON.parse(jsonLine)
91
+ } catch {
92
+ // Fallback: try to find URL in output
93
+ const urlMatch = output.match(/https?:\/\/critique\.work\/[^\s]+/)
94
+ if (urlMatch) {
95
+ await command.editReply({
96
+ content: `[diff](${urlMatch[0]})`,
97
+ })
98
+ logger.log(`Diff shared: ${urlMatch[0]}`)
99
+ return
100
+ }
101
+ await command.editReply({
102
+ content: 'No changes to show',
103
+ })
104
+ return
105
+ }
106
+
107
+ if (result.error || !result.url || !result.id) {
108
+ await command.editReply({
109
+ content: result.error || 'No changes to show',
110
+ })
111
+ return
112
+ }
113
+
114
+ const imageUrl = `https://critique.work/og/${result.id}.png`
115
+ const embed = new EmbedBuilder()
116
+ .setTitle(title)
117
+ .setURL(result.url)
118
+ .setImage(imageUrl)
119
+
120
+ await command.editReply({
121
+ embeds: [embed],
122
+ })
123
+ logger.log(`Diff shared: ${result.url}`)
124
+ } catch (error) {
125
+ logger.error('[DIFF] Error:', error)
126
+
127
+ // exec error includes stdout/stderr - try to parse JSON from it
128
+ const execError = error as {
129
+ stdout?: string
130
+ stderr?: string
131
+ message?: string
132
+ }
133
+ const output = execError.stdout || execError.stderr || ''
134
+
135
+ // Check if critique output JSON even on error
136
+ const lines = output.trim().split('\n')
137
+ const jsonLine = lines[lines.length - 1]
138
+ if (jsonLine) {
139
+ try {
140
+ const result = JSON.parse(jsonLine) as { error?: string }
141
+ if (result.error) {
142
+ await command.editReply({
143
+ content: result.error,
144
+ })
145
+ return
146
+ }
147
+ } catch {
148
+ // not JSON, continue to generic error
149
+ }
150
+ }
151
+
152
+ // Check for common errors
153
+ const message = execError.message || 'Unknown error'
154
+ if (message.includes('command not found') || message.includes('ENOENT')) {
155
+ await command.editReply({
156
+ content: 'bunx/critique not available',
157
+ })
158
+ return
159
+ }
160
+
161
+ await command.editReply({
162
+ content: `Failed to generate diff: ${message.slice(0, 200)}`,
163
+ })
164
+ }
165
+ }