@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.
- package/bin.js +2 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/bin.js +87 -0
- package/dist/bot-token.js +121 -0
- package/dist/bot-token.test.js +134 -0
- package/dist/channel-management.js +101 -0
- package/dist/cli-parsing.test.js +89 -0
- package/dist/cli.js +2529 -0
- package/dist/commands/abort.js +82 -0
- package/dist/commands/action-buttons.js +257 -0
- package/dist/commands/add-project.js +114 -0
- package/dist/commands/agent.js +291 -0
- package/dist/commands/ask-question.js +223 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +118 -0
- package/dist/commands/diff.js +128 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork.js +217 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +490 -0
- package/dist/commands/mention-mode.js +51 -0
- package/dist/commands/merge-worktree.js +124 -0
- package/dist/commands/model.js +694 -0
- package/dist/commands/permissions.js +163 -0
- package/dist/commands/queue.js +217 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +116 -0
- package/dist/commands/resume.js +159 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +192 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +159 -0
- package/dist/commands/unset-model.js +152 -0
- package/dist/commands/upgrade.js +42 -0
- package/dist/commands/user-command.js +148 -0
- package/dist/commands/verbosity.js +60 -0
- package/dist/commands/worktree-settings.js +50 -0
- package/dist/commands/worktree.js +299 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +110 -0
- package/dist/database.js +1050 -0
- package/dist/db.js +159 -0
- package/dist/db.test.js +49 -0
- package/dist/discord-api.js +28 -0
- package/dist/discord-auth.js +231 -0
- package/dist/discord-auth.test.js +80 -0
- package/dist/discord-bot.js +997 -0
- package/dist/discord-utils.js +560 -0
- package/dist/discord-utils.test.js +115 -0
- package/dist/errors.js +167 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/format-tables.js +122 -0
- package/dist/format-tables.test.js +199 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +117 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-consumer.js +232 -0
- package/dist/gateway-consumer.test.js +18 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +35 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +30 -0
- package/dist/generated/internal/class.js +41 -0
- package/dist/generated/internal/prismaNamespace.js +239 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +95 -0
- package/dist/hrana-server.js +416 -0
- package/dist/hrana-server.test.js +368 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +327 -0
- package/dist/ipc-polling.js +251 -0
- package/dist/kimaki-digital-twin.e2e.test.js +165 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +160 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +253 -0
- package/dist/message-formatting.js +433 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/openai-realtime.js +228 -0
- package/dist/opencode-plugin-loading.e2e.test.js +91 -0
- package/dist/opencode-plugin.js +536 -0
- package/dist/opencode-plugin.test.js +98 -0
- package/dist/opencode.js +409 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/runtime-mode.js +51 -0
- package/dist/runtime-mode.test.js +115 -0
- package/dist/sentry.js +127 -0
- package/dist/session-handler/state.js +151 -0
- package/dist/session-handler.js +1874 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/startup-service.js +153 -0
- package/dist/system-message.js +499 -0
- package/dist/task-runner.js +282 -0
- package/dist/task-schedule.js +191 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +781 -0
- package/dist/tools.js +359 -0
- package/dist/unnest-code-blocks.js +136 -0
- package/dist/unnest-code-blocks.test.js +641 -0
- package/dist/upgrade.js +114 -0
- package/dist/utils.js +109 -0
- package/dist/voice-handler.js +606 -0
- package/dist/voice.js +304 -0
- package/dist/voice.test.js +187 -0
- package/dist/wait-session.js +94 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-utils.js +727 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +82 -0
- package/schema.prisma +246 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +129 -0
- package/skills/errore/SKILL.md +589 -0
- package/skills/goke/.prettierrc +5 -0
- package/skills/goke/CHANGELOG.md +40 -0
- package/skills/goke/LICENSE +21 -0
- package/skills/goke/README.md +666 -0
- package/skills/goke/SKILL.md +458 -0
- package/skills/goke/package.json +43 -0
- package/skills/goke/src/__test__/coerce.test.ts +411 -0
- package/skills/goke/src/__test__/index.test.ts +1798 -0
- package/skills/goke/src/__test__/types.test-d.ts +111 -0
- package/skills/goke/src/coerce.ts +547 -0
- package/skills/goke/src/goke.ts +1362 -0
- package/skills/goke/src/index.ts +16 -0
- package/skills/goke/src/mri.ts +164 -0
- package/skills/goke/tsconfig.json +15 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/playwriter/SKILL.md +31 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +250 -0
- package/skills/zustand-centralized-state/SKILL.md +582 -0
- package/src/__snapshots__/compact-session-context-no-system.md +35 -0
- package/src/__snapshots__/compact-session-context.md +41 -0
- package/src/__snapshots__/first-session-no-info.md +17 -0
- package/src/__snapshots__/first-session-with-info.md +23 -0
- package/src/__snapshots__/session-1.md +17 -0
- package/src/__snapshots__/session-2.md +5871 -0
- package/src/__snapshots__/session-3.md +17 -0
- package/src/__snapshots__/session-with-tools.md +5871 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +282 -0
- package/src/ai-tool.ts +39 -0
- package/src/bin.ts +108 -0
- package/src/bot-token.test.ts +171 -0
- package/src/bot-token.ts +159 -0
- package/src/channel-management.ts +172 -0
- package/src/cli-parsing.test.ts +132 -0
- package/src/cli.ts +3605 -0
- package/src/commands/abort.ts +112 -0
- package/src/commands/action-buttons.ts +376 -0
- package/src/commands/add-project.ts +152 -0
- package/src/commands/agent.ts +404 -0
- package/src/commands/ask-question.ts +330 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +179 -0
- package/src/commands/diff.ts +165 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork.ts +320 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +634 -0
- package/src/commands/mention-mode.ts +77 -0
- package/src/commands/merge-worktree.ts +177 -0
- package/src/commands/model.ts +961 -0
- package/src/commands/permissions.ts +261 -0
- package/src/commands/queue.ts +296 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +242 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +250 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +221 -0
- package/src/commands/unset-model.ts +189 -0
- package/src/commands/upgrade.ts +52 -0
- package/src/commands/user-command.ts +193 -0
- package/src/commands/verbosity.ts +88 -0
- package/src/commands/worktree-settings.ts +79 -0
- package/src/commands/worktree.ts +431 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +148 -0
- package/src/database.ts +1530 -0
- package/src/db.test.ts +60 -0
- package/src/db.ts +190 -0
- package/src/discord-api.ts +35 -0
- package/src/discord-bot.ts +1316 -0
- package/src/discord-utils.test.ts +132 -0
- package/src/discord-utils.ts +767 -0
- package/src/errors.ts +213 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/format-tables.test.ts +223 -0
- package/src/format-tables.ts +145 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +176 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +109 -0
- package/src/generated/client.ts +131 -0
- package/src/generated/commonInputTypes.ts +512 -0
- package/src/generated/enums.ts +46 -0
- package/src/generated/internal/class.ts +362 -0
- package/src/generated/internal/prismaNamespace.ts +2251 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1577 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +2104 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1224 -0
- package/src/generated/models/channel_worktrees.ts +1308 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1599 -0
- package/src/generated/models/thread_worktrees.ts +1352 -0
- package/src/generated/models.ts +29 -0
- package/src/heap-monitor.ts +121 -0
- package/src/hrana-server.test.ts +428 -0
- package/src/hrana-server.ts +547 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +461 -0
- package/src/ipc-polling.ts +325 -0
- package/src/kimaki-digital-twin.e2e.test.ts +201 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +203 -0
- package/src/markdown.test.ts +360 -0
- package/src/markdown.ts +410 -0
- package/src/message-formatting.test.ts +81 -0
- package/src/message-formatting.ts +549 -0
- package/src/openai-realtime.ts +362 -0
- package/src/opencode-plugin-loading.e2e.test.ts +112 -0
- package/src/opencode-plugin.test.ts +108 -0
- package/src/opencode-plugin.ts +652 -0
- package/src/opencode.ts +554 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/schema.sql +158 -0
- package/src/sentry.ts +137 -0
- package/src/session-handler/state.ts +232 -0
- package/src/session-handler.ts +2668 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/startup-service.ts +200 -0
- package/src/system-message.ts +568 -0
- package/src/task-runner.ts +425 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +287 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +997 -0
- package/src/tools.ts +432 -0
- package/src/unnest-code-blocks.test.ts +679 -0
- package/src/unnest-code-blocks.ts +168 -0
- package/src/upgrade.ts +127 -0
- package/src/utils.ts +145 -0
- package/src/voice-handler.ts +852 -0
- package/src/voice.test.ts +219 -0
- package/src/voice.ts +444 -0
- package/src/wait-session.ts +147 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-utils.ts +988 -0
- package/src/xml.test.ts +38 -0
- 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
|
+
}
|