kimaki 0.27.0 → 0.28.0
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/agent-model.e2e.test.js +247 -64
- package/dist/anthropic-account-identity.js +8 -1
- package/dist/anthropic-auth-plugin.js +9 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +6 -1
- package/dist/cli-commands/session.js +139 -82
- package/dist/cli-commands/task.js +21 -14
- package/dist/cli-parsing.test.js +24 -1
- package/dist/cli-runner.js +16 -4
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +158 -24
- package/dist/commands/ask-question.js +2 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/merge-worktree.js +13 -6
- package/dist/commands/model-variant.js +3 -2
- package/dist/commands/model.js +4 -3
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +28 -0
- package/dist/db.js +11 -0
- package/dist/db.test.js +100 -0
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +501 -474
- package/dist/discord-command-registration.js +16 -9
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +84 -0
- package/dist/discord-utils.test.js +135 -1
- package/dist/errors.js +5 -0
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +12 -7
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +2 -2
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/logger.js +1 -0
- package/dist/message-formatting.js +163 -44
- package/dist/message-formatting.test.js +473 -13
- package/dist/message-preprocessing.js +105 -1
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +49 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +29 -3
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +1 -1
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +5 -5
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-footer.e2e.test.js +29 -32
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +9 -7
- package/dist/queue-advanced-question.e2e.test.js +2 -2
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +20 -20
- package/dist/queue-question-select-drain.e2e.test.js +7 -7
- package/dist/runtime-lifecycle.e2e.test.js +16 -19
- package/dist/schema.js +1 -1
- package/dist/session-handler/event-stream-state.js +124 -0
- package/dist/session-handler/event-stream-state.test.js +322 -1
- package/dist/session-handler/model-utils.js +16 -3
- package/dist/session-handler/model-utils.test.js +103 -3
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +340 -135
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-title-rename.test.js +30 -0
- package/dist/sleep.e2e.test.js +14 -11
- package/dist/system-message.js +45 -12
- package/dist/system-message.test.js +117 -10
- package/dist/task-runner.js +2 -1
- package/dist/task-schedule.js +32 -2
- package/dist/task-schedule.test.js +79 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -54
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +41 -1
- package/dist/worktree-lifecycle.e2e.test.js +30 -30
- package/package.json +8 -8
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +56 -232
- package/skills/holocron/SKILL.md +117 -17
- package/src/agent-model.e2e.test.ts +293 -66
- package/src/anthropic-account-identity.ts +19 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +11 -16
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +6 -1
- package/src/cli-commands/session.ts +171 -105
- package/src/cli-commands/task.ts +22 -15
- package/src/cli-parsing.test.ts +34 -1
- package/src/cli-runner.ts +16 -3
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +199 -23
- package/src/commands/ask-question.ts +2 -1
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/merge-worktree.ts +13 -6
- package/src/commands/model-variant.ts +3 -2
- package/src/commands/model.ts +4 -3
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +40 -0
- package/src/db.test.ts +105 -0
- package/src/db.ts +12 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +43 -7
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +27 -11
- package/src/discord-utils.test.ts +157 -0
- package/src/discord-utils.ts +131 -1
- package/src/errors.ts +7 -0
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +16 -9
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +5 -5
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/logger.ts +1 -0
- package/src/message-formatting.test.ts +586 -13
- package/src/message-formatting.ts +223 -46
- package/src/message-preprocessing.ts +116 -1
- package/src/opencode-command.test.ts +59 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +44 -4
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +1 -1
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +5 -5
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-footer.e2e.test.ts +30 -31
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +9 -7
- package/src/queue-advanced-question.e2e.test.ts +2 -2
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +20 -20
- package/src/queue-question-select-drain.e2e.test.ts +7 -7
- package/src/runtime-lifecycle.e2e.test.ts +16 -18
- package/src/schema.sql +1 -1
- package/src/schema.ts +1 -1
- package/src/session-handler/event-stream-state.test.ts +353 -0
- package/src/session-handler/event-stream-state.ts +179 -0
- package/src/session-handler/model-utils.test.ts +125 -3
- package/src/session-handler/model-utils.ts +36 -4
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +427 -158
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-title-rename.test.ts +45 -0
- package/src/sleep.e2e.test.ts +14 -11
- package/src/store.ts +1 -0
- package/src/system-message.test.ts +141 -10
- package/src/system-message.ts +66 -11
- package/src/task-runner.ts +2 -1
- package/src/task-schedule.test.ts +94 -0
- package/src/task-schedule.ts +47 -2
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -53
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +44 -0
- package/src/voice.ts +34 -6
- package/src/worktree-lifecycle.e2e.test.ts +30 -30
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { extendBashToolDefinition, } from './bash-tool-schema-plugin.js';
|
|
3
|
+
describe('extendBashToolDefinition', () => {
|
|
4
|
+
test('keeps parameters and assigns jsonSchema from bashParameters', () => {
|
|
5
|
+
const parameters = { keep: true };
|
|
6
|
+
const output = {
|
|
7
|
+
description: 'Execute a shell command',
|
|
8
|
+
parameters,
|
|
9
|
+
};
|
|
10
|
+
extendBashToolDefinition(output);
|
|
11
|
+
expect(output.parameters).toBe(parameters);
|
|
12
|
+
expect(output.jsonSchema).toMatchInlineSnapshot(`
|
|
13
|
+
{
|
|
14
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"properties": {
|
|
17
|
+
"command": {
|
|
18
|
+
"description": "The command to execute",
|
|
19
|
+
"type": "string",
|
|
20
|
+
},
|
|
21
|
+
"description": {
|
|
22
|
+
"description": "Short 5-10 word summary shown in Discord when the command is longer than 50 characters",
|
|
23
|
+
"type": "string",
|
|
24
|
+
},
|
|
25
|
+
"hasSideEffect": {
|
|
26
|
+
"description": "True if the command writes files, modifies state, installs packages, or triggers external effects",
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
},
|
|
29
|
+
"timeout": {
|
|
30
|
+
"description": "Optional timeout in milliseconds",
|
|
31
|
+
"maximum": 9007199254740991,
|
|
32
|
+
"minimum": -9007199254740991,
|
|
33
|
+
"type": "integer",
|
|
34
|
+
},
|
|
35
|
+
"workdir": {
|
|
36
|
+
"description": "Working directory. Defaults to the current directory.",
|
|
37
|
+
"type": "string",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
"required": [
|
|
41
|
+
"command",
|
|
42
|
+
"description",
|
|
43
|
+
"hasSideEffect",
|
|
44
|
+
],
|
|
45
|
+
"type": "object",
|
|
46
|
+
}
|
|
47
|
+
`);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -12,7 +12,7 @@ import { spawn, execSync } from 'node:child_process';
|
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
14
|
import { getDefaultKimakiDirectory } from '../channel-management.js';
|
|
15
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, findRegisteredTextChannelForDirectory, formatProjectAlreadyRegisteredError } from '../database.js';
|
|
16
16
|
import { ShareMarkdown } from '../markdown.js';
|
|
17
17
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
18
18
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -39,8 +39,15 @@ cli
|
|
|
39
39
|
cliLogger.error(`Directory does not exist: ${absolutePath}`);
|
|
40
40
|
process.exit(EXIT_NO_RESTART);
|
|
41
41
|
}
|
|
42
|
-
// Initialize database
|
|
43
42
|
await initDatabase();
|
|
43
|
+
const existingChannel = await findRegisteredTextChannelForDirectory(absolutePath);
|
|
44
|
+
if (existingChannel) {
|
|
45
|
+
cliLogger.error(formatProjectAlreadyRegisteredError({
|
|
46
|
+
channelId: existingChannel.channel_id,
|
|
47
|
+
directory: absolutePath,
|
|
48
|
+
}));
|
|
49
|
+
process.exit(EXIT_NO_RESTART);
|
|
50
|
+
}
|
|
44
51
|
const { token: botToken, appId } = await resolveBotCredentials({
|
|
45
52
|
appIdOverride: options.appId,
|
|
46
53
|
});
|
|
@@ -62,30 +69,6 @@ cli
|
|
|
62
69
|
});
|
|
63
70
|
cliLogger.log('Finding guild...');
|
|
64
71
|
const guild = await resolveGuildForProjectCommand({ client, guildIdOverride: options.guild });
|
|
65
|
-
// Check if channel already exists in this guild
|
|
66
|
-
cliLogger.log('Checking for existing channel...');
|
|
67
|
-
try {
|
|
68
|
-
const existingChannels = await findChannelsByDirectory({
|
|
69
|
-
directory: absolutePath,
|
|
70
|
-
channelType: 'text',
|
|
71
|
-
});
|
|
72
|
-
for (const existingChannel of existingChannels) {
|
|
73
|
-
try {
|
|
74
|
-
const ch = await client.channels.fetch(existingChannel.channel_id);
|
|
75
|
-
if (ch && !ch.isDMBased() && ch.guild.id === guild.id) {
|
|
76
|
-
void client.destroy();
|
|
77
|
-
cliLogger.error(`Channel already exists for this directory in ${guild.name}. Channel ID: ${existingChannel.channel_id}`);
|
|
78
|
-
process.exit(EXIT_NO_RESTART);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
cliLogger.debug(`Failed to fetch channel ${existingChannel.channel_id} while checking existing channels:`, error instanceof Error ? error.stack : String(error));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
cliLogger.debug('Database lookup failed while checking existing channels:', error instanceof Error ? error.stack : String(error));
|
|
88
|
-
}
|
|
89
72
|
const { textChannelId, voiceChannelId, channelName } = await createProjectChannels({
|
|
90
73
|
guild,
|
|
91
74
|
projectDirectory: absolutePath,
|
|
@@ -131,7 +114,8 @@ cli
|
|
|
131
114
|
}
|
|
132
115
|
catch (error) {
|
|
133
116
|
// Only mark as deleted for Unknown Channel (10003) or 404,
|
|
134
|
-
// not transient errors like rate limits or 5xx
|
|
117
|
+
// not transient errors like rate limits or 5xx.
|
|
118
|
+
// Gateway-proxy must return Discord 404 for unknown channels, not 403.
|
|
135
119
|
const code = error instanceof Error ? Reflect.get(error, 'code') : undefined;
|
|
136
120
|
const status = error instanceof Error ? Reflect.get(error, 'status') : undefined;
|
|
137
121
|
const isUnknownChannel = code === 10003 || status === 404;
|
|
@@ -21,6 +21,7 @@ import { ShareMarkdown } from '../markdown.js';
|
|
|
21
21
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
22
22
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
23
23
|
import { WORKTREE_PREFIX } from '../commands/merge-worktree.js';
|
|
24
|
+
import { QUEUE_PREFIX } from '../message-formatting.js';
|
|
24
25
|
import { buildOpencodeEventLogLine } from '../session-handler/opencode-session-event-log.js';
|
|
25
26
|
import { createDiscordRest } from '../discord-urls.js';
|
|
26
27
|
import { archiveThread, ensureThreadMember, uploadFilesToDiscord, stripMentions } from '../discord-utils.js';
|
|
@@ -394,6 +395,10 @@ cli
|
|
|
394
395
|
}
|
|
395
396
|
const threadPromptMarker = {
|
|
396
397
|
start: true,
|
|
398
|
+
...(threadTargetUser && {
|
|
399
|
+
userId: threadTargetUser.id,
|
|
400
|
+
...(threadTargetUser.username && { username: threadTargetUser.username }),
|
|
401
|
+
}),
|
|
397
402
|
...(options.agent && { agent: options.agent }),
|
|
398
403
|
...(options.model && { model: options.model }),
|
|
399
404
|
...(options.permission?.length ? { permissions: options.permission } : {}),
|
|
@@ -409,7 +414,7 @@ cli
|
|
|
409
414
|
// Prefix the prompt so it's clear who sent it (matches /queue format).
|
|
410
415
|
// Use a newline between prefix and prompt so leading /command
|
|
411
416
|
// detection can find the command on its own line.
|
|
412
|
-
const prefixedPrompt =
|
|
417
|
+
const prefixedPrompt = `${QUEUE_PREFIX}**kimaki-cli:**\n${prompt}`;
|
|
413
418
|
if (threadTargetUser) {
|
|
414
419
|
cliLogger.log(`Adding user ${threadTargetUser.username || threadTargetUser.id} to thread...`);
|
|
415
420
|
const addMemberResult = await ensureThreadMember({
|
|
@@ -12,9 +12,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
import { spawn, execSync } from 'node:child_process';
|
|
13
13
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
14
14
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
15
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, getThreadWorktreeOrWorkspace } from '../database.js';
|
|
15
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, getThreadWorktreeOrWorkspace, getAllTextChannelDirectories } from '../database.js';
|
|
16
16
|
import { ShareMarkdown } from '../markdown.js';
|
|
17
|
-
import { parseSessionSearchPattern,
|
|
17
|
+
import { parseSessionSearchPattern, collectSessionSearchMatches, validateSessionSearchScope, resolveSessionSearchDirectories, parseSessionSearchDays, sessionSearchMinUpdated, SESSION_SEARCH_DEFAULT_DAYS } from '../session-search.js';
|
|
18
18
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
19
19
|
import { formatTimeAgo } from '../commands/worktrees.js';
|
|
20
20
|
import { editorsForFile, loadFileEditEvents } from '../file-edit-log.js';
|
|
@@ -319,16 +319,26 @@ cli
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
cli
|
|
322
|
-
.command('session search <query>',
|
|
322
|
+
.command('session search <query>', `Search past sessions for text or /regex/flags. Defaults to the last ${SESSION_SEARCH_DEFAULT_DAYS} days; use --days 0 for all time. Add --all for every locally registered project.`)
|
|
323
323
|
.option('--project <path>', 'Project directory (defaults to cwd)')
|
|
324
324
|
.option('--channel <channelId>', 'Resolve project from a Discord channel ID')
|
|
325
|
+
.option('--all', 'Search every locally registered project')
|
|
326
|
+
.option('--days <n>', `Only search sessions updated in the last n days (default: ${SESSION_SEARCH_DEFAULT_DAYS}; 0 = all time)`)
|
|
325
327
|
.option('--limit <n>', 'Maximum matched sessions to return (default: 20)')
|
|
326
328
|
.option('--json', 'Output as JSON')
|
|
329
|
+
.example('kimaki session search "auth timeout"')
|
|
330
|
+
.example('kimaki session search "auth timeout" --days 0')
|
|
331
|
+
.example('kimaki session search "auth timeout" --all')
|
|
327
332
|
.action(async (query, options) => {
|
|
328
333
|
try {
|
|
329
334
|
await initDatabase();
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
const scopeError = validateSessionSearchScope({
|
|
336
|
+
all: options.all,
|
|
337
|
+
project: options.project,
|
|
338
|
+
channel: options.channel,
|
|
339
|
+
});
|
|
340
|
+
if (scopeError) {
|
|
341
|
+
cliLogger.error(scopeError.message);
|
|
332
342
|
process.exit(EXIT_NO_RESTART);
|
|
333
343
|
}
|
|
334
344
|
const limit = (() => {
|
|
@@ -343,7 +353,16 @@ cli
|
|
|
343
353
|
cliLogger.error(limit.message);
|
|
344
354
|
process.exit(EXIT_NO_RESTART);
|
|
345
355
|
}
|
|
346
|
-
const
|
|
356
|
+
const days = parseSessionSearchDays(typeof options.days === 'string' ? options.days : undefined);
|
|
357
|
+
if (days instanceof Error) {
|
|
358
|
+
cliLogger.error(days.message);
|
|
359
|
+
process.exit(EXIT_NO_RESTART);
|
|
360
|
+
}
|
|
361
|
+
const minUpdated = sessionSearchMinUpdated({ days });
|
|
362
|
+
const explicitDirectory = await (async () => {
|
|
363
|
+
if (options.all) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
347
366
|
if (options.channel) {
|
|
348
367
|
const channelConfig = await getChannelDirectory(options.channel);
|
|
349
368
|
if (!channelConfig) {
|
|
@@ -351,15 +370,45 @@ cli
|
|
|
351
370
|
}
|
|
352
371
|
return path.resolve(channelConfig.directory);
|
|
353
372
|
}
|
|
354
|
-
|
|
373
|
+
if (options.project) {
|
|
374
|
+
return path.resolve(options.project);
|
|
375
|
+
}
|
|
376
|
+
return undefined;
|
|
355
377
|
})();
|
|
356
|
-
if (
|
|
357
|
-
cliLogger.error(
|
|
378
|
+
if (explicitDirectory instanceof Error) {
|
|
379
|
+
cliLogger.error(explicitDirectory.message);
|
|
380
|
+
process.exit(EXIT_NO_RESTART);
|
|
381
|
+
}
|
|
382
|
+
const registeredDirectories = options.all
|
|
383
|
+
? (await getAllTextChannelDirectories()).map((directory) => {
|
|
384
|
+
return path.resolve(directory);
|
|
385
|
+
})
|
|
386
|
+
: [];
|
|
387
|
+
const projectDirectories = resolveSessionSearchDirectories({
|
|
388
|
+
all: Boolean(options.all),
|
|
389
|
+
registeredDirectories,
|
|
390
|
+
cwd: path.resolve('.'),
|
|
391
|
+
explicitDirectory,
|
|
392
|
+
});
|
|
393
|
+
if (projectDirectories instanceof Error) {
|
|
394
|
+
cliLogger.error(projectDirectories.message);
|
|
395
|
+
process.exit(EXIT_NO_RESTART);
|
|
396
|
+
}
|
|
397
|
+
const existingDirectories = [];
|
|
398
|
+
for (const directory of projectDirectories) {
|
|
399
|
+
if (fs.existsSync(directory)) {
|
|
400
|
+
existingDirectories.push(directory);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (options.all) {
|
|
404
|
+
cliLogger.warn(`Skipping missing directory: ${directory}`);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
cliLogger.error(`Directory does not exist: ${directory}`);
|
|
358
408
|
process.exit(EXIT_NO_RESTART);
|
|
359
409
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
cliLogger.error(`Directory does not exist: ${projectDirectory}`);
|
|
410
|
+
if (existingDirectories.length === 0) {
|
|
411
|
+
cliLogger.error('No searchable project directories found. Add a project with `kimaki project add`, or pass --project.');
|
|
363
412
|
process.exit(EXIT_NO_RESTART);
|
|
364
413
|
}
|
|
365
414
|
const searchPattern = parseSessionSearchPattern(query);
|
|
@@ -367,15 +416,43 @@ cli
|
|
|
367
416
|
cliLogger.error(searchPattern.message);
|
|
368
417
|
process.exit(EXIT_NO_RESTART);
|
|
369
418
|
}
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
419
|
+
const clientsBySessionId = new Map();
|
|
420
|
+
const searchedDirectories = [];
|
|
421
|
+
const searchableSessions = [];
|
|
422
|
+
const listedDirectories = await Promise.all(existingDirectories.map(async (projectDirectory) => {
|
|
423
|
+
cliLogger.log(`Connecting to OpenCode server for ${projectDirectory}...`);
|
|
424
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
425
|
+
if (getClient instanceof Error) {
|
|
426
|
+
return { projectDirectory, getClient, sessions: [] };
|
|
427
|
+
}
|
|
428
|
+
const sessionsResponse = await getClient().session.list();
|
|
429
|
+
return {
|
|
430
|
+
projectDirectory,
|
|
431
|
+
getClient,
|
|
432
|
+
sessions: sessionsResponse.data || [],
|
|
433
|
+
};
|
|
434
|
+
}));
|
|
435
|
+
for (const listed of listedDirectories) {
|
|
436
|
+
if (listed.getClient instanceof Error) {
|
|
437
|
+
if (options.all) {
|
|
438
|
+
cliLogger.warn(`Skipping ${listed.projectDirectory}: failed to connect to OpenCode: ${listed.getClient.message}`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
cliLogger.error('Failed to connect to OpenCode:', listed.getClient.message);
|
|
442
|
+
process.exit(EXIT_NO_RESTART);
|
|
443
|
+
}
|
|
444
|
+
searchedDirectories.push(listed.projectDirectory);
|
|
445
|
+
for (const session of listed.sessions) {
|
|
446
|
+
clientsBySessionId.set(session.id, listed.getClient);
|
|
447
|
+
searchableSessions.push({
|
|
448
|
+
id: session.id,
|
|
449
|
+
title: session.title || 'Untitled Session',
|
|
450
|
+
directory: session.directory || listed.projectDirectory,
|
|
451
|
+
updated: session.time.updated,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
375
454
|
}
|
|
376
|
-
|
|
377
|
-
const sessions = sessionsResponse.data || [];
|
|
378
|
-
if (sessions.length === 0) {
|
|
455
|
+
if (searchableSessions.length === 0) {
|
|
379
456
|
cliLogger.log('No sessions found');
|
|
380
457
|
process.exit(0);
|
|
381
458
|
}
|
|
@@ -386,82 +463,62 @@ cli
|
|
|
386
463
|
const sessionToThread = new Map(threadSessions
|
|
387
464
|
.filter((row) => row.session_id !== '')
|
|
388
465
|
.map((row) => [row.session_id, row.thread_id]));
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
466
|
+
const scopeLabel = options.all
|
|
467
|
+
? `${searchedDirectories.length} project(s)`
|
|
468
|
+
: searchedDirectories[0] || path.resolve('.');
|
|
469
|
+
const daysLabel = days === 0 ? 'all time' : `the last ${days} day${days === 1 ? '' : 's'}`;
|
|
470
|
+
const printMatch = (match) => {
|
|
471
|
+
const threadInfo = match.threadId ? ` | thread: ${match.threadId}` : '';
|
|
472
|
+
console.log(`${match.id} | ${match.title} | ${match.updated} | ${match.source}${threadInfo}`);
|
|
473
|
+
console.log(` Directory: ${match.directory}`);
|
|
474
|
+
match.snippets.forEach((snippet) => {
|
|
475
|
+
console.log(` - ${snippet}`);
|
|
398
476
|
});
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
477
|
+
};
|
|
478
|
+
let printedHeader = false;
|
|
479
|
+
const { matches: matchedSessions, scannedSessions } = await collectSessionSearchMatches({
|
|
480
|
+
sessions: searchableSessions,
|
|
481
|
+
searchPattern,
|
|
482
|
+
sessionToThread,
|
|
483
|
+
limit,
|
|
484
|
+
minUpdated,
|
|
485
|
+
onMatch: options.json
|
|
486
|
+
? undefined
|
|
487
|
+
: (match) => {
|
|
488
|
+
if (!printedHeader) {
|
|
489
|
+
printedHeader = true;
|
|
490
|
+
cliLogger.log(`Found matching session(s) for ${searchPattern.raw} in ${scopeLabel} (${daysLabel})`);
|
|
491
|
+
}
|
|
492
|
+
printMatch(match);
|
|
493
|
+
},
|
|
494
|
+
loadMessages: async (session) => {
|
|
495
|
+
const getClient = clientsBySessionId.get(session.id);
|
|
496
|
+
if (!getClient) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
const messagesResponse = await getClient().session.messages({
|
|
500
|
+
sessionID: session.id,
|
|
422
501
|
});
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const threadId = sessionToThread.get(session.id);
|
|
429
|
-
matchedSessions.push({
|
|
430
|
-
id: session.id,
|
|
431
|
-
title: session.title || 'Untitled Session',
|
|
432
|
-
directory: session.directory,
|
|
433
|
-
updated: new Date(session.time.updated).toISOString(),
|
|
434
|
-
source: threadId ? 'kimaki' : 'opencode',
|
|
435
|
-
threadId: threadId || null,
|
|
436
|
-
snippets,
|
|
437
|
-
});
|
|
438
|
-
if (matchedSessions.length >= limit) {
|
|
439
|
-
break;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
502
|
+
return messagesResponse.data || [];
|
|
503
|
+
},
|
|
504
|
+
});
|
|
442
505
|
if (options.json) {
|
|
443
506
|
console.log(JSON.stringify({
|
|
444
507
|
query: searchPattern.raw,
|
|
445
508
|
mode: searchPattern.mode,
|
|
446
|
-
|
|
509
|
+
all: Boolean(options.all),
|
|
510
|
+
days,
|
|
511
|
+
projectDirectories: searchedDirectories,
|
|
447
512
|
scannedSessions,
|
|
448
513
|
matches: matchedSessions,
|
|
449
514
|
}, null, 2));
|
|
450
515
|
process.exit(0);
|
|
451
516
|
}
|
|
452
517
|
if (matchedSessions.length === 0) {
|
|
453
|
-
|
|
518
|
+
const cutoffHint = days === 0 ? '' : '. Use --days 0 to search all time';
|
|
519
|
+
cliLogger.log(`No matches found for ${searchPattern.raw} in ${scopeLabel} (${scannedSessions} sessions scanned, ${daysLabel})${cutoffHint}`);
|
|
454
520
|
process.exit(0);
|
|
455
521
|
}
|
|
456
|
-
cliLogger.log(`Found ${matchedSessions.length} matching session(s) for ${searchPattern.raw} in ${projectDirectory}`);
|
|
457
|
-
for (const match of matchedSessions) {
|
|
458
|
-
const threadInfo = match.threadId ? ` | thread: ${match.threadId}` : '';
|
|
459
|
-
console.log(`${match.id} | ${match.title} | ${match.updated} | ${match.source}${threadInfo}`);
|
|
460
|
-
console.log(` Directory: ${match.directory}`);
|
|
461
|
-
match.snippets.forEach((snippet) => {
|
|
462
|
-
console.log(` - ${snippet}`);
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
522
|
process.exit(0);
|
|
466
523
|
}
|
|
467
524
|
catch (error) {
|
|
@@ -22,7 +22,7 @@ import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-u
|
|
|
22
22
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js';
|
|
23
23
|
import { execAsync, validateWorktreeDirectory } from '../worktrees.js';
|
|
24
24
|
import { upgrade, getCurrentVersion } from '../upgrade.js';
|
|
25
|
-
import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload } from '../task-schedule.js';
|
|
25
|
+
import { applyScheduledTaskUserEdit, getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload } from '../task-schedule.js';
|
|
26
26
|
import { EXIT_NO_RESTART, formatMemberLookupUnavailableMessage, formatRelativeTime, formatTaskScheduleLine, getDiscordUserIdFromUserOption, isDiscordMemberLookupUnavailable, isGuildMemberSearchResult, isThreadChannelType, printDiscordInstallUrlAndExit, resolveBotCredentials, resolveDiscordUserOption, sendDiscordMessageWithOptionalAttachment, } from '../cli-runner.js';
|
|
27
27
|
import { validateCliModelOption } from '../session-handler/model-utils.js';
|
|
28
28
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
@@ -135,7 +135,7 @@ cli
|
|
|
135
135
|
.option('--model <model>', 'Model for the scheduled session, format provider/model (empty string clears)')
|
|
136
136
|
.option('--pre-run <command>', 'New pre-run command (empty string clears)')
|
|
137
137
|
.option('--allow-concurrency <enabled>', z.enum(['true', 'false']).describe('Allow concurrent sessions: true or false'))
|
|
138
|
-
.option('-u, --user <user>', 'Discord user ID, mention, or username added to the task thread')
|
|
138
|
+
.option('-u, --user <user>', 'Discord user ID, mention, or username added to the task thread (empty string clears)')
|
|
139
139
|
.action(async (id, options) => {
|
|
140
140
|
try {
|
|
141
141
|
const trimmedPrompt = options.prompt === undefined ? undefined : options.prompt.trim();
|
|
@@ -143,9 +143,10 @@ cli
|
|
|
143
143
|
const hasModel = options.model !== undefined;
|
|
144
144
|
const hasPreRun = options.preRun !== undefined;
|
|
145
145
|
const hasAllowConcurrency = options.allowConcurrency !== undefined;
|
|
146
|
+
const hasUser = options.user !== undefined;
|
|
146
147
|
if (!trimmedPrompt &&
|
|
147
148
|
!options.sendAt &&
|
|
148
|
-
!
|
|
149
|
+
!hasUser &&
|
|
149
150
|
!hasAgent &&
|
|
150
151
|
!hasModel &&
|
|
151
152
|
!hasPreRun &&
|
|
@@ -181,7 +182,7 @@ cli
|
|
|
181
182
|
cliLogger.error(`Failed to parse task payload: ${existingPayload.message}`);
|
|
182
183
|
process.exit(EXIT_NO_RESTART);
|
|
183
184
|
}
|
|
184
|
-
const resolvedUser = options.user
|
|
185
|
+
const resolvedUser = hasUser && options.user.trim()
|
|
185
186
|
? await resolveTaskUser({
|
|
186
187
|
user: options.user,
|
|
187
188
|
channelId: task.channel_id,
|
|
@@ -207,9 +208,6 @@ cli
|
|
|
207
208
|
const updatedPayload = {
|
|
208
209
|
...existingPayload,
|
|
209
210
|
prompt: newPrompt,
|
|
210
|
-
...(resolvedUser
|
|
211
|
-
? { userId: resolvedUser.id, username: resolvedUser.username || null }
|
|
212
|
-
: {}),
|
|
213
211
|
...(hasAgent ? { agent: options.agent.trim() || null } : {}),
|
|
214
212
|
...(hasModel ? { model: nextModel } : {}),
|
|
215
213
|
...(hasPreRun ? { preRunCommand: options.preRun.trim() || null } : {}),
|
|
@@ -217,9 +215,16 @@ cli
|
|
|
217
215
|
? { allowConcurrency: options.allowConcurrency === 'true' }
|
|
218
216
|
: {}),
|
|
219
217
|
};
|
|
218
|
+
const payloadWithUser = hasUser
|
|
219
|
+
? applyScheduledTaskUserEdit({
|
|
220
|
+
payload: updatedPayload,
|
|
221
|
+
userOption: options.user,
|
|
222
|
+
resolvedUser,
|
|
223
|
+
})
|
|
224
|
+
: updatedPayload;
|
|
220
225
|
const updateData = {
|
|
221
226
|
taskId,
|
|
222
|
-
payloadJson: serializeScheduledTaskPayload(
|
|
227
|
+
payloadJson: serializeScheduledTaskPayload(payloadWithUser),
|
|
223
228
|
promptPreview: getPromptPreview(newPrompt),
|
|
224
229
|
};
|
|
225
230
|
if (options.sendAt) {
|
|
@@ -245,19 +250,21 @@ cli
|
|
|
245
250
|
process.exit(EXIT_NO_RESTART);
|
|
246
251
|
}
|
|
247
252
|
const parts = [`Updated task ${taskId}`];
|
|
248
|
-
if (
|
|
249
|
-
parts.push(
|
|
253
|
+
if (hasUser) {
|
|
254
|
+
parts.push(payloadWithUser.userId
|
|
255
|
+
? `user ${payloadWithUser.username || payloadWithUser.userId} will be added to the thread`
|
|
256
|
+
: 'user cleared; nobody will be added to the thread');
|
|
250
257
|
}
|
|
251
258
|
if (hasAgent) {
|
|
252
|
-
parts.push(`agent=${
|
|
259
|
+
parts.push(`agent=${payloadWithUser.agent || '-'}`);
|
|
253
260
|
}
|
|
254
261
|
if (hasModel) {
|
|
255
|
-
parts.push(`model=${
|
|
262
|
+
parts.push(`model=${payloadWithUser.model || '-'}`);
|
|
256
263
|
}
|
|
257
264
|
if (hasPreRun)
|
|
258
|
-
parts.push(`preRun=${
|
|
265
|
+
parts.push(`preRun=${payloadWithUser.preRunCommand || '-'}`);
|
|
259
266
|
if (hasAllowConcurrency) {
|
|
260
|
-
parts.push(`allowConcurrency=${
|
|
267
|
+
parts.push(`allowConcurrency=${payloadWithUser.allowConcurrency}`);
|
|
261
268
|
}
|
|
262
269
|
cliLogger.log(parts.join(' | '));
|
|
263
270
|
process.exit(0);
|
package/dist/cli-parsing.test.js
CHANGED
|
@@ -9,10 +9,11 @@ async function parseWithGoke(argv) {
|
|
|
9
9
|
"cli.command('send', 'Send a message').option('-c, --channel <channelId>', 'Discord channel ID').option('--thread <threadId>', 'Thread ID').option('--session <sessionId>', 'Session ID').option('--send-at <schedule>', 'Schedule')",
|
|
10
10
|
"cli.command('session archive <threadId>', 'Archive a thread')",
|
|
11
11
|
"cli.command('session title <title>', 'Update title').option('--session <sessionId>', 'Session ID').option('--thread <threadId>', 'Thread ID')",
|
|
12
|
-
"cli.command('session search <query>', 'Search sessions').option('--channel <channelId>', 'Discord channel ID').option('--project <path>', 'Project path')",
|
|
12
|
+
"cli.command('session search <query>', 'Search sessions').option('--channel <channelId>', 'Discord channel ID').option('--project <path>', 'Project path').option('--all', 'Search all registered projects').option('--days <n>', 'Only search recent sessions')",
|
|
13
13
|
"cli.command('session export-events-jsonl', 'Export in-memory events to JSONL').option('--session <sessionId>', 'Session ID').option('--out <file>', 'Output path')",
|
|
14
14
|
"cli.command('add-project', 'Add a project').option('-g, --guild <guildId>', 'Discord guild/server ID')",
|
|
15
15
|
"cli.command('task delete <id>', 'Delete task')",
|
|
16
|
+
"cli.command('task edit <id>', 'Edit task').option('-u, --user <user>', 'Discord user')",
|
|
16
17
|
"cli.command('multioauth anthropic list', 'List stored Anthropic accounts')",
|
|
17
18
|
"cli.command('multioauth anthropic remove <indexOrEmail>', 'Remove stored Anthropic account')",
|
|
18
19
|
"cli.command('multioauth openai list', 'List stored OpenAI accounts')",
|
|
@@ -112,6 +113,15 @@ describe('goke CLI ID parsing', () => {
|
|
|
112
113
|
expect(result.options.channel).toBe(channelId);
|
|
113
114
|
expect(typeof result.options.channel).toBe('string');
|
|
114
115
|
});
|
|
116
|
+
test('parses session search --all as a boolean', async () => {
|
|
117
|
+
const result = await parseWithGoke(['node', 'kimaki', 'session', 'search', 'auth timeout', '--all']);
|
|
118
|
+
expect(result.args[0]).toBe('auth timeout');
|
|
119
|
+
expect(result.options.all).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
test('parses session search --days as a string', async () => {
|
|
122
|
+
const result = await parseWithGoke(['node', 'kimaki', 'session', 'search', 'auth timeout', '--days', '0']);
|
|
123
|
+
expect(result.options.days).toBe('0');
|
|
124
|
+
});
|
|
115
125
|
test('keeps session export options as strings', async () => {
|
|
116
126
|
const sessionId = '001111222233334444';
|
|
117
127
|
const outPath = './tmp/session-events.jsonl';
|
|
@@ -142,6 +152,19 @@ describe('goke CLI ID parsing', () => {
|
|
|
142
152
|
expect(result.args[0]).toBe(taskId);
|
|
143
153
|
expect(typeof result.args[0]).toBe('string');
|
|
144
154
|
});
|
|
155
|
+
test('parses empty --user on task edit so the stored user can be cleared', async () => {
|
|
156
|
+
const result = await parseWithGoke([
|
|
157
|
+
'node',
|
|
158
|
+
'kimaki',
|
|
159
|
+
'task',
|
|
160
|
+
'edit',
|
|
161
|
+
'11',
|
|
162
|
+
'--user',
|
|
163
|
+
'',
|
|
164
|
+
]);
|
|
165
|
+
expect(result.args[0]).toBe('11');
|
|
166
|
+
expect(result.options.user).toBe('');
|
|
167
|
+
});
|
|
145
168
|
test('multioauth account remove parses index and email as strings', async () => {
|
|
146
169
|
const indexResult = await parseWithGoke(['node', 'kimaki', 'multioauth', 'anthropic', 'remove', '2']);
|
|
147
170
|
const emailResult = await parseWithGoke(['node', 'kimaki', 'multioauth', 'openai', 'remove', 'user@example.com']);
|
package/dist/cli-runner.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keeps cli.ts focused on command composition while preserving the bot onboarding flow.
|
|
3
3
|
import { intro, outro, text, password, note, cancel, isCancel, confirm, log, multiselect, select, spinner, } from '@clack/prompts';
|
|
4
4
|
import { deduplicateByKey, generateBotInstallUrl, generateDiscordInstallUrlForBot, KIMAKI_GATEWAY_APP_ID, KIMAKI_WEBSITE_URL, abbreviatePath, } from './utils.js';
|
|
5
|
-
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
5
|
+
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, assertCompatibleOpencodeVersion, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
6
6
|
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getDb, } from './database.js';
|
|
7
7
|
import * as orm from 'drizzle-orm';
|
|
8
8
|
import * as dbSchema from './schema.js';
|
|
@@ -767,16 +767,23 @@ export function showReadyMessage({ kimakiChannels, createdChannels, }) {
|
|
|
767
767
|
note('Leave this process running to keep the bot active.\n\nIf you close this process or restart your machine, run `npx kimaki` again to start the bot.', '⚠️ Keep Running');
|
|
768
768
|
}
|
|
769
769
|
/**
|
|
770
|
-
* Create
|
|
770
|
+
* Create default channels in locally configured guilds, or proxy-authorized gateway guilds.
|
|
771
771
|
* Idempotent: skips guilds that already have the channel.
|
|
772
772
|
* Extracted so both the interactive and headless startup paths share the same logic.
|
|
773
773
|
*/
|
|
774
774
|
export async function ensureDefaultChannelsWithWelcome({ guilds, discordClient, appId, isGatewayMode, installerDiscordUserId, }) {
|
|
775
775
|
if (process.env['KIMAKI_NO_DEFAULT_CHANNEL'] === '1')
|
|
776
776
|
return [];
|
|
777
|
+
const localMappings = isGatewayMode ? [] : await findChannelsByDirectory({});
|
|
777
778
|
const created = [];
|
|
778
779
|
for (const guild of guilds) {
|
|
779
780
|
try {
|
|
781
|
+
if (!isGatewayMode) {
|
|
782
|
+
// Match live channel IDs: older local mappings have no guild_id.
|
|
783
|
+
const channels = await guild.channels.fetch();
|
|
784
|
+
if (!localMappings.some((row) => channels.has(row.channel_id)))
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
780
787
|
const result = await createDefaultKimakiChannel({
|
|
781
788
|
guild,
|
|
782
789
|
botName: discordClient.user?.username,
|
|
@@ -1148,6 +1155,11 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1148
1155
|
possiblePathsWindows: ['~\\.bun\\bin\\bun.exe'],
|
|
1149
1156
|
}),
|
|
1150
1157
|
]);
|
|
1158
|
+
const opencodeVersionCheck = await assertCompatibleOpencodeVersion();
|
|
1159
|
+
if (opencodeVersionCheck instanceof Error) {
|
|
1160
|
+
cliLogger.error(opencodeVersionCheck.message);
|
|
1161
|
+
process.exit(EXIT_NO_RESTART);
|
|
1162
|
+
}
|
|
1151
1163
|
if (store.getState().autoUpgradeEnabled) {
|
|
1152
1164
|
void backgroundUpgradeKimaki();
|
|
1153
1165
|
}
|
|
@@ -1351,7 +1363,7 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1351
1363
|
catch (error) {
|
|
1352
1364
|
cliLogger.warn('Background channel sync failed:', error instanceof Error ? error.stack : String(error));
|
|
1353
1365
|
}
|
|
1354
|
-
// Create default
|
|
1366
|
+
// Create default channels only in locally configured or gateway-authorized guilds.
|
|
1355
1367
|
// Runs after channel sync so existing channels are detected correctly.
|
|
1356
1368
|
try {
|
|
1357
1369
|
await ensureDefaultChannelsWithWelcome({
|
|
@@ -1506,7 +1518,7 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1506
1518
|
}
|
|
1507
1519
|
}
|
|
1508
1520
|
// Create default kimaki channel for general-purpose tasks.
|
|
1509
|
-
//
|
|
1521
|
+
// Only locally configured or gateway-authorized guilds are eligible.
|
|
1510
1522
|
const defaultChannelResults = await ensureDefaultChannelsWithWelcome({
|
|
1511
1523
|
guilds,
|
|
1512
1524
|
discordClient,
|