kimaki 0.4.50 ā 0.4.51
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/dist/channel-management.js +15 -11
- package/dist/cli.js +4 -1
- package/dist/commands/add-project.js +2 -1
- package/dist/commands/create-new-project.js +2 -1
- package/dist/discord-bot.js +11 -0
- package/package.json +1 -1
- package/src/channel-management.ts +23 -14
- package/src/cli.ts +6 -1
- package/src/commands/add-project.ts +2 -1
- package/src/commands/create-new-project.ts +3 -2
- package/src/discord-bot.ts +14 -0
|
@@ -40,34 +40,38 @@ export async function ensureKimakiAudioCategory(guild, botName) {
|
|
|
40
40
|
type: ChannelType.GuildCategory,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
-
export async function createProjectChannels({ guild, projectDirectory, appId, botName, }) {
|
|
43
|
+
export async function createProjectChannels({ guild, projectDirectory, appId, botName, enableVoiceChannels = false, }) {
|
|
44
44
|
const baseName = path.basename(projectDirectory);
|
|
45
45
|
const channelName = `${baseName}`
|
|
46
46
|
.toLowerCase()
|
|
47
47
|
.replace(/[^a-z0-9-]/g, '-')
|
|
48
48
|
.slice(0, 100);
|
|
49
49
|
const kimakiCategory = await ensureKimakiCategory(guild, botName);
|
|
50
|
-
const kimakiAudioCategory = await ensureKimakiAudioCategory(guild, botName);
|
|
51
50
|
const textChannel = await guild.channels.create({
|
|
52
51
|
name: channelName,
|
|
53
52
|
type: ChannelType.GuildText,
|
|
54
53
|
parent: kimakiCategory,
|
|
55
54
|
// Channel configuration is stored in SQLite, not in the topic
|
|
56
55
|
});
|
|
57
|
-
const voiceChannel = await guild.channels.create({
|
|
58
|
-
name: channelName,
|
|
59
|
-
type: ChannelType.GuildVoice,
|
|
60
|
-
parent: kimakiAudioCategory,
|
|
61
|
-
});
|
|
62
56
|
getDatabase()
|
|
63
57
|
.prepare('INSERT OR REPLACE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)')
|
|
64
58
|
.run(textChannel.id, projectDirectory, 'text', appId);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
let voiceChannelId = null;
|
|
60
|
+
if (enableVoiceChannels) {
|
|
61
|
+
const kimakiAudioCategory = await ensureKimakiAudioCategory(guild, botName);
|
|
62
|
+
const voiceChannel = await guild.channels.create({
|
|
63
|
+
name: channelName,
|
|
64
|
+
type: ChannelType.GuildVoice,
|
|
65
|
+
parent: kimakiAudioCategory,
|
|
66
|
+
});
|
|
67
|
+
getDatabase()
|
|
68
|
+
.prepare('INSERT OR REPLACE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)')
|
|
69
|
+
.run(voiceChannel.id, projectDirectory, 'voice', appId);
|
|
70
|
+
voiceChannelId = voiceChannel.id;
|
|
71
|
+
}
|
|
68
72
|
return {
|
|
69
73
|
textChannelId: textChannel.id,
|
|
70
|
-
voiceChannelId
|
|
74
|
+
voiceChannelId,
|
|
71
75
|
channelName,
|
|
72
76
|
};
|
|
73
77
|
}
|
package/dist/cli.js
CHANGED
|
@@ -428,7 +428,7 @@ async function backgroundInit({ currentDir, token, appId, }) {
|
|
|
428
428
|
cliLogger.error('Background init failed:', error instanceof Error ? error.message : String(error));
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
|
-
async function run({ restart, addChannels, useWorktrees }) {
|
|
431
|
+
async function run({ restart, addChannels, useWorktrees, enableVoiceChannels }) {
|
|
432
432
|
startCaffeinate();
|
|
433
433
|
const forceSetup = Boolean(restart);
|
|
434
434
|
intro('š¤ Discord Bot Setup');
|
|
@@ -777,6 +777,7 @@ async function run({ restart, addChannels, useWorktrees }) {
|
|
|
777
777
|
projectDirectory: project.worktree,
|
|
778
778
|
appId,
|
|
779
779
|
botName: discordClient.user?.username,
|
|
780
|
+
enableVoiceChannels,
|
|
780
781
|
});
|
|
781
782
|
createdChannels.push({
|
|
782
783
|
name: channelName,
|
|
@@ -823,6 +824,7 @@ cli
|
|
|
823
824
|
.option('--data-dir <path>', 'Data directory for config and database (default: ~/.kimaki)')
|
|
824
825
|
.option('--install-url', 'Print the bot install URL and exit')
|
|
825
826
|
.option('--use-worktrees', 'Create git worktrees for all new sessions started from channel messages')
|
|
827
|
+
.option('--enable-voice-channels', 'Create voice channels for projects (disabled by default)')
|
|
826
828
|
.option('--verbosity <level>', 'Default verbosity for all channels (tools-and-text, text-and-essential-tools, or text-only)')
|
|
827
829
|
.action(async (options) => {
|
|
828
830
|
try {
|
|
@@ -859,6 +861,7 @@ cli
|
|
|
859
861
|
addChannels: options.addChannels,
|
|
860
862
|
dataDir: options.dataDir,
|
|
861
863
|
useWorktrees: options.useWorktrees,
|
|
864
|
+
enableVoiceChannels: options.enableVoiceChannels,
|
|
862
865
|
});
|
|
863
866
|
}
|
|
864
867
|
catch (error) {
|
|
@@ -52,7 +52,8 @@ export async function handleAddProjectCommand({ command, appId }) {
|
|
|
52
52
|
appId,
|
|
53
53
|
botName: command.client.user?.username,
|
|
54
54
|
});
|
|
55
|
-
|
|
55
|
+
const voiceInfo = voiceChannelId ? `\nš Voice: <#${voiceChannelId}>` : '';
|
|
56
|
+
await command.editReply(`ā
Created channels for project:\nš Text: <#${textChannelId}>${voiceInfo}\nš Directory: \`${directory}\``);
|
|
56
57
|
logger.log(`Created channels for project ${channelName} at ${directory}`);
|
|
57
58
|
}
|
|
58
59
|
catch (error) {
|
|
@@ -83,7 +83,8 @@ export async function handleCreateNewProjectCommand({ command, appId, }) {
|
|
|
83
83
|
}
|
|
84
84
|
const { textChannelId, voiceChannelId, channelName, projectDirectory, sanitizedName } = result;
|
|
85
85
|
const textChannel = (await guild.channels.fetch(textChannelId));
|
|
86
|
-
|
|
86
|
+
const voiceInfo = voiceChannelId ? `\nš Voice: <#${voiceChannelId}>` : '';
|
|
87
|
+
await command.editReply(`ā
Created new project **${sanitizedName}**\nš Directory: \`${projectDirectory}\`\nš Text: <#${textChannelId}>${voiceInfo}\n_Starting session..._`);
|
|
87
88
|
const starterMessage = await textChannel.send({
|
|
88
89
|
content: `š **New project initialized**\nš \`${projectDirectory}\``,
|
|
89
90
|
flags: SILENT_MESSAGE_FLAGS,
|
package/dist/discord-bot.js
CHANGED
|
@@ -117,6 +117,17 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
if (message.guild && message.member) {
|
|
120
|
+
// Check for "no-kimaki" role first - blocks user regardless of other permissions.
|
|
121
|
+
// This implements the "four-eyes principle": even owners must remove this role
|
|
122
|
+
// to use the bot, adding friction to prevent accidental usage.
|
|
123
|
+
const hasNoKimakiRole = message.member.roles.cache.some((role) => role.name.toLowerCase() === 'no-kimaki');
|
|
124
|
+
if (hasNoKimakiRole) {
|
|
125
|
+
await message.reply({
|
|
126
|
+
content: `You have the **no-kimaki** role which blocks bot access.\nRemove this role to use Kimaki.`,
|
|
127
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
120
131
|
const isOwner = message.member.id === message.guild.ownerId;
|
|
121
132
|
const isAdmin = message.member.permissions.has(PermissionsBitField.Flags.Administrator);
|
|
122
133
|
const canManageServer = message.member.permissions.has(PermissionsBitField.Flags.ManageGuild);
|
package/package.json
CHANGED
|
@@ -63,12 +63,14 @@ export async function createProjectChannels({
|
|
|
63
63
|
projectDirectory,
|
|
64
64
|
appId,
|
|
65
65
|
botName,
|
|
66
|
+
enableVoiceChannels = false,
|
|
66
67
|
}: {
|
|
67
68
|
guild: Guild
|
|
68
69
|
projectDirectory: string
|
|
69
70
|
appId: string
|
|
70
71
|
botName?: string
|
|
71
|
-
|
|
72
|
+
enableVoiceChannels?: boolean
|
|
73
|
+
}): Promise<{ textChannelId: string; voiceChannelId: string | null; channelName: string }> {
|
|
72
74
|
const baseName = path.basename(projectDirectory)
|
|
73
75
|
const channelName = `${baseName}`
|
|
74
76
|
.toLowerCase()
|
|
@@ -76,7 +78,6 @@ export async function createProjectChannels({
|
|
|
76
78
|
.slice(0, 100)
|
|
77
79
|
|
|
78
80
|
const kimakiCategory = await ensureKimakiCategory(guild, botName)
|
|
79
|
-
const kimakiAudioCategory = await ensureKimakiAudioCategory(guild, botName)
|
|
80
81
|
|
|
81
82
|
const textChannel = await guild.channels.create({
|
|
82
83
|
name: channelName,
|
|
@@ -85,27 +86,35 @@ export async function createProjectChannels({
|
|
|
85
86
|
// Channel configuration is stored in SQLite, not in the topic
|
|
86
87
|
})
|
|
87
88
|
|
|
88
|
-
const voiceChannel = await guild.channels.create({
|
|
89
|
-
name: channelName,
|
|
90
|
-
type: ChannelType.GuildVoice,
|
|
91
|
-
parent: kimakiAudioCategory,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
89
|
getDatabase()
|
|
95
90
|
.prepare(
|
|
96
91
|
'INSERT OR REPLACE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
|
|
97
92
|
)
|
|
98
93
|
.run(textChannel.id, projectDirectory, 'text', appId)
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
95
|
+
let voiceChannelId: string | null = null
|
|
96
|
+
|
|
97
|
+
if (enableVoiceChannels) {
|
|
98
|
+
const kimakiAudioCategory = await ensureKimakiAudioCategory(guild, botName)
|
|
99
|
+
|
|
100
|
+
const voiceChannel = await guild.channels.create({
|
|
101
|
+
name: channelName,
|
|
102
|
+
type: ChannelType.GuildVoice,
|
|
103
|
+
parent: kimakiAudioCategory,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
getDatabase()
|
|
107
|
+
.prepare(
|
|
108
|
+
'INSERT OR REPLACE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
|
|
109
|
+
)
|
|
110
|
+
.run(voiceChannel.id, projectDirectory, 'voice', appId)
|
|
111
|
+
|
|
112
|
+
voiceChannelId = voiceChannel.id
|
|
113
|
+
}
|
|
105
114
|
|
|
106
115
|
return {
|
|
107
116
|
textChannelId: textChannel.id,
|
|
108
|
-
voiceChannelId
|
|
117
|
+
voiceChannelId,
|
|
109
118
|
channelName,
|
|
110
119
|
}
|
|
111
120
|
}
|
package/src/cli.ts
CHANGED
|
@@ -209,6 +209,7 @@ type CliOptions = {
|
|
|
209
209
|
addChannels?: boolean
|
|
210
210
|
dataDir?: string
|
|
211
211
|
useWorktrees?: boolean
|
|
212
|
+
enableVoiceChannels?: boolean
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
// Commands to skip when registering user commands (reserved names)
|
|
@@ -595,7 +596,7 @@ async function backgroundInit({
|
|
|
595
596
|
}
|
|
596
597
|
}
|
|
597
598
|
|
|
598
|
-
async function run({ restart, addChannels, useWorktrees }: CliOptions) {
|
|
599
|
+
async function run({ restart, addChannels, useWorktrees, enableVoiceChannels }: CliOptions) {
|
|
599
600
|
startCaffeinate()
|
|
600
601
|
|
|
601
602
|
const forceSetup = Boolean(restart)
|
|
@@ -1057,6 +1058,7 @@ async function run({ restart, addChannels, useWorktrees }: CliOptions) {
|
|
|
1057
1058
|
projectDirectory: project.worktree,
|
|
1058
1059
|
appId,
|
|
1059
1060
|
botName: discordClient.user?.username,
|
|
1061
|
+
enableVoiceChannels,
|
|
1060
1062
|
})
|
|
1061
1063
|
|
|
1062
1064
|
createdChannels.push({
|
|
@@ -1123,6 +1125,7 @@ cli
|
|
|
1123
1125
|
.option('--data-dir <path>', 'Data directory for config and database (default: ~/.kimaki)')
|
|
1124
1126
|
.option('--install-url', 'Print the bot install URL and exit')
|
|
1125
1127
|
.option('--use-worktrees', 'Create git worktrees for all new sessions started from channel messages')
|
|
1128
|
+
.option('--enable-voice-channels', 'Create voice channels for projects (disabled by default)')
|
|
1126
1129
|
.option('--verbosity <level>', 'Default verbosity for all channels (tools-and-text, text-and-essential-tools, or text-only)')
|
|
1127
1130
|
.action(
|
|
1128
1131
|
async (options: {
|
|
@@ -1131,6 +1134,7 @@ cli
|
|
|
1131
1134
|
dataDir?: string
|
|
1132
1135
|
installUrl?: boolean
|
|
1133
1136
|
useWorktrees?: boolean
|
|
1137
|
+
enableVoiceChannels?: boolean
|
|
1134
1138
|
verbosity?: string
|
|
1135
1139
|
}) => {
|
|
1136
1140
|
try {
|
|
@@ -1172,6 +1176,7 @@ cli
|
|
|
1172
1176
|
addChannels: options.addChannels,
|
|
1173
1177
|
dataDir: options.dataDir,
|
|
1174
1178
|
useWorktrees: options.useWorktrees,
|
|
1179
|
+
enableVoiceChannels: options.enableVoiceChannels,
|
|
1175
1180
|
})
|
|
1176
1181
|
} catch (error) {
|
|
1177
1182
|
cliLogger.error('Unhandled error:', error instanceof Error ? error.message : String(error))
|
|
@@ -72,8 +72,9 @@ export async function handleAddProjectCommand({ command, appId }: CommandContext
|
|
|
72
72
|
botName: command.client.user?.username,
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
+
const voiceInfo = voiceChannelId ? `\nš Voice: <#${voiceChannelId}>` : ''
|
|
75
76
|
await command.editReply(
|
|
76
|
-
`ā
Created channels for project:\nš Text: <#${textChannelId}
|
|
77
|
+
`ā
Created channels for project:\nš Text: <#${textChannelId}>${voiceInfo}\nš Directory: \`${directory}\``,
|
|
77
78
|
)
|
|
78
79
|
|
|
79
80
|
logger.log(`Created channels for project ${channelName} at ${directory}`)
|
|
@@ -31,7 +31,7 @@ export async function createNewProject({
|
|
|
31
31
|
botName?: string
|
|
32
32
|
}): Promise<{
|
|
33
33
|
textChannelId: string
|
|
34
|
-
voiceChannelId: string
|
|
34
|
+
voiceChannelId: string | null
|
|
35
35
|
channelName: string
|
|
36
36
|
projectDirectory: string
|
|
37
37
|
sanitizedName: string
|
|
@@ -124,8 +124,9 @@ export async function handleCreateNewProjectCommand({
|
|
|
124
124
|
const { textChannelId, voiceChannelId, channelName, projectDirectory, sanitizedName } = result
|
|
125
125
|
const textChannel = (await guild.channels.fetch(textChannelId)) as TextChannel
|
|
126
126
|
|
|
127
|
+
const voiceInfo = voiceChannelId ? `\nš Voice: <#${voiceChannelId}>` : ''
|
|
127
128
|
await command.editReply(
|
|
128
|
-
`ā
Created new project **${sanitizedName}**\nš Directory: \`${projectDirectory}\`\nš Text: <#${textChannelId}
|
|
129
|
+
`ā
Created new project **${sanitizedName}**\nš Directory: \`${projectDirectory}\`\nš Text: <#${textChannelId}>${voiceInfo}\n_Starting session..._`,
|
|
129
130
|
)
|
|
130
131
|
|
|
131
132
|
const starterMessage = await textChannel.send({
|
package/src/discord-bot.ts
CHANGED
|
@@ -193,6 +193,20 @@ export async function startDiscordBot({
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
if (message.guild && message.member) {
|
|
196
|
+
// Check for "no-kimaki" role first - blocks user regardless of other permissions.
|
|
197
|
+
// This implements the "four-eyes principle": even owners must remove this role
|
|
198
|
+
// to use the bot, adding friction to prevent accidental usage.
|
|
199
|
+
const hasNoKimakiRole = message.member.roles.cache.some(
|
|
200
|
+
(role) => role.name.toLowerCase() === 'no-kimaki',
|
|
201
|
+
)
|
|
202
|
+
if (hasNoKimakiRole) {
|
|
203
|
+
await message.reply({
|
|
204
|
+
content: `You have the **no-kimaki** role which blocks bot access.\nRemove this role to use Kimaki.`,
|
|
205
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
206
|
+
})
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
196
210
|
const isOwner = message.member.id === message.guild.ownerId
|
|
197
211
|
const isAdmin = message.member.permissions.has(PermissionsBitField.Flags.Administrator)
|
|
198
212
|
const canManageServer = message.member.permissions.has(
|