kimaki 0.4.82 ā 0.4.84
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/LICENSE +21 -0
- package/dist/anthropic-auth-plugin.js +7 -0
- package/dist/cli.js +51 -7
- package/dist/commands/abort.js +5 -16
- package/dist/commands/action-buttons.js +3 -3
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/ask-question.js +3 -3
- package/dist/commands/context-usage.js +1 -1
- package/dist/commands/create-new-project.js +1 -1
- package/dist/commands/fork.js +11 -8
- package/dist/commands/merge-worktree.js +1 -1
- package/dist/commands/new-worktree.js +63 -44
- package/dist/commands/remove-project.js +1 -1
- package/dist/commands/resume.js +11 -8
- package/dist/commands/screenshare.js +14 -6
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session.js +1 -1
- package/dist/commands/undo-redo.js +91 -7
- package/dist/commands/user-command.js +1 -1
- package/dist/config.js +16 -1
- package/dist/database.js +53 -2
- package/dist/db.js +6 -0
- package/dist/discord-bot.js +48 -85
- package/dist/discord-command-registration.js +1 -1
- package/dist/external-opencode-sync.js +515 -0
- package/dist/external-opencode-sync.test.js +151 -0
- package/dist/gateway-proxy.e2e.test.js +8 -5
- package/dist/genai.js +1 -1
- package/dist/generated/enums.js +4 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.js +1 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
- package/dist/generated/models/external_session_pending_prompts.js +1 -0
- package/dist/hrana-server.js +14 -285
- package/dist/hrana-server.test.js +4 -2
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/kitty-graphics-parser.js +3 -0
- package/dist/kitty-graphics-parser.test.js +276 -0
- package/dist/kitty-graphics-plugin.js +3 -0
- package/dist/markdown.js +4 -4
- package/dist/markdown.test.js +1 -1
- package/dist/message-formatting.js +54 -15
- package/dist/onboarding-tutorial.js +1 -1
- package/dist/openai-realtime.js +9 -13
- package/dist/opencode.js +28 -5
- package/dist/queue-advanced-e2e-setup.js +89 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
- package/dist/queue-advanced-typing.e2e.test.js +9 -22
- package/dist/queue-question-select-drain.e2e.test.js +117 -0
- package/dist/session-handler/event-stream-state.js +101 -7
- package/dist/session-handler/event-stream-state.test.js +7 -3
- package/dist/session-handler/thread-session-runtime.js +120 -9
- package/dist/store.js +1 -0
- package/dist/system-message.js +22 -4
- package/dist/system-message.test.js +19 -0
- package/dist/task-runner.js +1 -1
- package/dist/thread-message-queue.e2e.test.js +8 -14
- package/dist/tools.js +1 -1
- package/dist/undo-redo.e2e.test.js +20 -25
- package/package.json +10 -6
- package/schema.prisma +6 -0
- package/skills/errore/SKILL.md +40 -13
- package/skills/goke/SKILL.md +12 -0
- package/skills/lintcn/SKILL.md +868 -0
- package/skills/npm-package/SKILL.md +1 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/spiceflow/SKILL.md +1 -1
- package/skills/usecomputer/SKILL.md +339 -0
- package/src/ai-tool-to-genai.ts +1 -0
- package/src/anthropic-auth-plugin.ts +7 -0
- package/src/cli.ts +59 -6
- package/src/commands/abort.ts +6 -16
- package/src/commands/action-buttons.ts +5 -1
- package/src/commands/add-project.ts +1 -1
- package/src/commands/ask-question.ts +5 -2
- package/src/commands/context-usage.ts +1 -1
- package/src/commands/create-new-project.ts +1 -1
- package/src/commands/fork.ts +12 -11
- package/src/commands/merge-worktree.ts +1 -1
- package/src/commands/new-worktree.ts +74 -55
- package/src/commands/remove-project.ts +1 -1
- package/src/commands/resume.ts +12 -10
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +18 -6
- package/src/commands/session.ts +1 -1
- package/src/commands/undo-redo.ts +108 -10
- package/src/commands/user-command.ts +1 -1
- package/src/config.ts +19 -1
- package/src/database.ts +72 -3
- package/src/db.ts +8 -0
- package/src/discord-bot.ts +58 -93
- package/src/discord-command-registration.ts +1 -1
- package/src/external-opencode-sync.ts +729 -0
- package/src/gateway-proxy.e2e.test.ts +9 -5
- package/src/genai.ts +3 -3
- package/src/generated/commonInputTypes.ts +34 -0
- package/src/generated/enums.ts +8 -0
- package/src/generated/internal/class.ts +4 -4
- package/src/generated/internal/prismaNamespace.ts +8 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
- package/src/generated/models/thread_sessions.ts +53 -1
- package/src/hrana-server.test.ts +8 -2
- package/src/hrana-server.ts +18 -390
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/markdown.test.ts +1 -1
- package/src/markdown.ts +4 -4
- package/src/message-formatting.ts +66 -17
- package/src/onboarding-tutorial.ts +1 -1
- package/src/openai-realtime.ts +6 -10
- package/src/opencode.ts +31 -7
- package/src/queue-advanced-e2e-setup.ts +92 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
- package/src/queue-advanced-typing.e2e.test.ts +9 -22
- package/src/queue-question-select-drain.e2e.test.ts +149 -0
- package/src/schema.sql +1 -0
- package/src/session-handler/event-stream-state.test.ts +7 -2
- package/src/session-handler/event-stream-state.ts +128 -7
- package/src/session-handler/thread-runtime-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +153 -11
- package/src/store.ts +8 -0
- package/src/system-message.ts +27 -4
- package/src/task-runner.ts +1 -1
- package/src/thread-message-queue.e2e.test.ts +8 -14
- package/src/tools.ts +1 -1
- package/src/undo-redo.e2e.test.ts +28 -26
- package/skills/jitter/node_modules/.bin/esbuild +0 -21
- package/skills/jitter/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
- package/skills/jitter/node_modules/typescript/README.md +0 -50
- package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
- package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
- package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
- package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
- package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
- package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
- package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
- package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
- package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
- package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
- package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
- package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
- package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
- package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/package.json +0 -120
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kimaki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic OAuth authentication plugin for OpenCode.
|
|
3
3
|
*
|
|
4
|
+
* If you're copy-pasting this plugin into your OpenCode config folder,
|
|
5
|
+
* you need to install the runtime dependencies first:
|
|
6
|
+
*
|
|
7
|
+
* cd ~/.config/opencode
|
|
8
|
+
* bun init -y
|
|
9
|
+
* bun add @openauthjs/openauth proper-lockfile
|
|
10
|
+
*
|
|
4
11
|
* Handles two concerns:
|
|
5
12
|
* 1. OAuth login + token refresh (PKCE flow against claude.ai)
|
|
6
13
|
* 2. Request/response rewriting (tool names, system prompt, beta headers)
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { intro, outro, text, password, note, cancel, isCancel, confirm, log, multiselect, select, spinner, } from '@clack/prompts';
|
|
8
8
|
import { deduplicateByKey, generateBotInstallUrl, generateDiscordInstallUrlForBot, KIMAKI_GATEWAY_APP_ID, KIMAKI_WEBSITE_URL, abbreviatePath, } from './utils.js';
|
|
9
9
|
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, ensureKimakiCategory, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
10
|
-
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getPrisma, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, } from './database.js';
|
|
10
|
+
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getPrisma, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, } from './database.js';
|
|
11
11
|
import { ShareMarkdown } from './markdown.js';
|
|
12
12
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts, } from './session-search.js';
|
|
13
13
|
import { formatWorktreeName } from './commands/new-worktree.js';
|
|
@@ -26,7 +26,7 @@ import { createLogger, formatErrorWithStack, initLogFile, LogPrefix } from './lo
|
|
|
26
26
|
import { initSentry, notifyError } from './sentry.js';
|
|
27
27
|
import { archiveThread, uploadFilesToDiscord, stripMentions, } from './discord-utils.js';
|
|
28
28
|
import { spawn, execSync } from 'node:child_process';
|
|
29
|
-
import { setDataDir, getDataDir, getProjectsDir, } from './config.js';
|
|
29
|
+
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir, } from './config.js';
|
|
30
30
|
import { execAsync } from './worktrees.js';
|
|
31
31
|
import { backgroundUpgradeKimaki, upgrade, getCurrentVersion, } from './upgrade.js';
|
|
32
32
|
import { startHranaServer } from './hrana-server.js';
|
|
@@ -1305,6 +1305,7 @@ cli
|
|
|
1305
1305
|
.option('--restart-onboarding', 'Prompt for new credentials even if saved')
|
|
1306
1306
|
.option('--add-channels', 'Select OpenCode projects to create Discord channels before starting')
|
|
1307
1307
|
.option('--data-dir <path>', 'Data directory for config and database (default: ~/.kimaki)')
|
|
1308
|
+
.option('--projects-dir <path>', 'Directory where new projects are created (default: <data-dir>/projects)')
|
|
1308
1309
|
.option('--install-url', 'Print the bot install URL and exit')
|
|
1309
1310
|
.option('--use-worktrees', 'Create git worktrees for all new sessions started from channel messages')
|
|
1310
1311
|
.option('--enable-voice-channels', 'Create voice channels for projects (disabled by default)')
|
|
@@ -1332,6 +1333,10 @@ cli
|
|
|
1332
1333
|
setDataDir(options.dataDir);
|
|
1333
1334
|
cliLogger.log(`Using data directory: ${getDataDir()}`);
|
|
1334
1335
|
}
|
|
1336
|
+
if (options.projectsDir) {
|
|
1337
|
+
setProjectsDir(options.projectsDir);
|
|
1338
|
+
cliLogger.log(`Using projects directory: ${getProjectsDir()}`);
|
|
1339
|
+
}
|
|
1335
1340
|
// Initialize file logging to <dataDir>/kimaki.log
|
|
1336
1341
|
initLogFile(getDataDir());
|
|
1337
1342
|
// Batch all CLI flag store updates into a single setState call.
|
|
@@ -2353,6 +2358,7 @@ cli
|
|
|
2353
2358
|
cli
|
|
2354
2359
|
.command('project list', 'List all registered projects with their Discord channels')
|
|
2355
2360
|
.option('--json', 'Output as JSON')
|
|
2361
|
+
.option('--prune', 'Remove stale entries whose Discord channel no longer exists')
|
|
2356
2362
|
.action(async (options) => {
|
|
2357
2363
|
await initDatabase();
|
|
2358
2364
|
const prisma = await getPrisma();
|
|
@@ -2369,31 +2375,67 @@ cli
|
|
|
2369
2375
|
const rest = botRow ? createDiscordRest(botRow.token) : null;
|
|
2370
2376
|
const enriched = await Promise.all(channels.map(async (ch) => {
|
|
2371
2377
|
let channelName = '';
|
|
2378
|
+
let deleted = false;
|
|
2372
2379
|
if (rest) {
|
|
2373
2380
|
try {
|
|
2374
2381
|
const data = (await rest.get(Routes.channel(ch.channel_id)));
|
|
2375
2382
|
channelName = data.name || '';
|
|
2376
2383
|
}
|
|
2377
|
-
catch {
|
|
2378
|
-
//
|
|
2384
|
+
catch (error) {
|
|
2385
|
+
// Only mark as deleted for Unknown Channel (10003) or 404,
|
|
2386
|
+
// not transient errors like rate limits or 5xx
|
|
2387
|
+
const isUnknownChannel = error instanceof Error &&
|
|
2388
|
+
'code' in error &&
|
|
2389
|
+
'status' in error &&
|
|
2390
|
+
(error.code === 10003 ||
|
|
2391
|
+
error.status === 404);
|
|
2392
|
+
deleted = isUnknownChannel;
|
|
2379
2393
|
}
|
|
2380
2394
|
}
|
|
2381
|
-
return { ...ch, channelName };
|
|
2395
|
+
return { ...ch, channelName, deleted };
|
|
2382
2396
|
}));
|
|
2397
|
+
// Prune stale entries if requested
|
|
2398
|
+
if (options.prune) {
|
|
2399
|
+
const stale = enriched.filter((ch) => {
|
|
2400
|
+
return ch.deleted;
|
|
2401
|
+
});
|
|
2402
|
+
if (stale.length === 0) {
|
|
2403
|
+
cliLogger.log('No stale channels to prune');
|
|
2404
|
+
}
|
|
2405
|
+
else {
|
|
2406
|
+
for (const ch of stale) {
|
|
2407
|
+
await deleteChannelDirectoryById(ch.channel_id);
|
|
2408
|
+
cliLogger.log(`Pruned stale channel ${ch.channel_id} (${path.basename(ch.directory)})`);
|
|
2409
|
+
}
|
|
2410
|
+
cliLogger.log(`Pruned ${stale.length} stale channel(s)`);
|
|
2411
|
+
}
|
|
2412
|
+
// Re-filter to only show live entries after pruning
|
|
2413
|
+
const live = enriched.filter((ch) => {
|
|
2414
|
+
return !ch.deleted;
|
|
2415
|
+
});
|
|
2416
|
+
if (live.length === 0) {
|
|
2417
|
+
cliLogger.log('No projects registered');
|
|
2418
|
+
process.exit(0);
|
|
2419
|
+
}
|
|
2420
|
+
enriched.length = 0;
|
|
2421
|
+
enriched.push(...live);
|
|
2422
|
+
}
|
|
2383
2423
|
if (options.json) {
|
|
2384
2424
|
const output = enriched.map((ch) => ({
|
|
2385
2425
|
channel_id: ch.channel_id,
|
|
2386
2426
|
channel_name: ch.channelName,
|
|
2387
2427
|
directory: ch.directory,
|
|
2388
2428
|
folder_name: path.basename(ch.directory),
|
|
2429
|
+
deleted: ch.deleted,
|
|
2389
2430
|
}));
|
|
2390
2431
|
console.log(JSON.stringify(output, null, 2));
|
|
2391
2432
|
process.exit(0);
|
|
2392
2433
|
}
|
|
2393
2434
|
for (const ch of enriched) {
|
|
2394
2435
|
const folderName = path.basename(ch.directory);
|
|
2436
|
+
const deletedTag = ch.deleted ? ' (deleted from Discord)' : '';
|
|
2395
2437
|
const channelLabel = ch.channelName ? `#${ch.channelName}` : ch.channel_id;
|
|
2396
|
-
console.log(`\n${channelLabel}`);
|
|
2438
|
+
console.log(`\n${channelLabel}${deletedTag}`);
|
|
2397
2439
|
console.log(` Folder: ${folderName}`);
|
|
2398
2440
|
console.log(` Directory: ${ch.directory}`);
|
|
2399
2441
|
console.log(` Channel ID: ${ch.channel_id}`);
|
|
@@ -2586,6 +2628,7 @@ cli
|
|
|
2586
2628
|
.option('-t, --tunnel-id [id]', 'Custom tunnel ID (only for services safe to expose publicly; prefer random default)')
|
|
2587
2629
|
.option('-h, --host [host]', 'Local host (default: localhost)')
|
|
2588
2630
|
.option('-s, --server [url]', 'Tunnel server URL')
|
|
2631
|
+
.option('-k, --kill', 'Kill any existing process on the port before starting')
|
|
2589
2632
|
.action(async (options) => {
|
|
2590
2633
|
const { runTunnel, parseCommandFromArgv, CLI_NAME } = await import('traforo/run-tunnel');
|
|
2591
2634
|
if (!options.port) {
|
|
@@ -2607,10 +2650,11 @@ cli
|
|
|
2607
2650
|
baseDomain: 'kimaki.xyz',
|
|
2608
2651
|
serverUrl: options.server,
|
|
2609
2652
|
command: command.length > 0 ? command : undefined,
|
|
2653
|
+
kill: options.kill,
|
|
2610
2654
|
});
|
|
2611
2655
|
});
|
|
2612
2656
|
cli
|
|
2613
|
-
.command('screenshare', 'Share your screen via VNC tunnel. Auto-stops after
|
|
2657
|
+
.command('screenshare', 'Share your screen via VNC tunnel. Auto-stops after 30 minutes. Runs until Ctrl+C. Use tmux to run in background.')
|
|
2614
2658
|
.action(async () => {
|
|
2615
2659
|
const { startScreenshare } = await import('./commands/screenshare.js');
|
|
2616
2660
|
try {
|
package/dist/commands/abort.js
CHANGED
|
@@ -27,23 +27,18 @@ export async function handleAbortCommand({ command, }) {
|
|
|
27
27
|
});
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
30
31
|
const resolved = await resolveWorkingDirectory({
|
|
31
32
|
channel: channel,
|
|
32
33
|
});
|
|
33
34
|
if (!resolved) {
|
|
34
|
-
await command.
|
|
35
|
-
content: 'Could not determine project directory for this channel',
|
|
36
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
37
|
-
});
|
|
35
|
+
await command.editReply('Could not determine project directory for this channel');
|
|
38
36
|
return;
|
|
39
37
|
}
|
|
40
38
|
const { projectDirectory } = resolved;
|
|
41
39
|
const sessionId = await getThreadSession(channel.id);
|
|
42
40
|
if (!sessionId) {
|
|
43
|
-
await command.
|
|
44
|
-
content: 'No active session in this thread',
|
|
45
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
46
|
-
});
|
|
41
|
+
await command.editReply('No active session in this thread');
|
|
47
42
|
return;
|
|
48
43
|
}
|
|
49
44
|
// abortActiveRun delegates to session.abort(), run settlement stays event-driven.
|
|
@@ -55,10 +50,7 @@ export async function handleAbortCommand({ command, }) {
|
|
|
55
50
|
// No runtime but session exists ā fall back to direct API abort
|
|
56
51
|
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
57
52
|
if (getClient instanceof Error) {
|
|
58
|
-
await command.
|
|
59
|
-
content: `Failed to abort: ${getClient.message}`,
|
|
60
|
-
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
61
|
-
});
|
|
53
|
+
await command.editReply(`Failed to abort: ${getClient.message}`);
|
|
62
54
|
return;
|
|
63
55
|
}
|
|
64
56
|
try {
|
|
@@ -68,9 +60,6 @@ export async function handleAbortCommand({ command, }) {
|
|
|
68
60
|
logger.error('[ABORT] API abort failed:', error);
|
|
69
61
|
}
|
|
70
62
|
}
|
|
71
|
-
await command.
|
|
72
|
-
content: `Request **aborted**`,
|
|
73
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
74
|
-
});
|
|
63
|
+
await command.editReply('Request **aborted**');
|
|
75
64
|
logger.log(`Session ${sessionId} aborted by user`);
|
|
76
65
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags, } from 'discord.js';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { getThreadSession } from '../database.js';
|
|
7
|
-
import { NOTIFY_MESSAGE_FLAGS, resolveWorkingDirectory, sendThreadMessage, } from '../discord-utils.js';
|
|
7
|
+
import { NOTIFY_MESSAGE_FLAGS, SILENT_MESSAGE_FLAGS, resolveWorkingDirectory, sendThreadMessage, } from '../discord-utils.js';
|
|
8
8
|
import { createLogger } from '../logger.js';
|
|
9
9
|
import { notifyError } from '../sentry.js';
|
|
10
10
|
import { getOrCreateRuntime, } from '../session-handler/thread-session-runtime.js';
|
|
@@ -101,7 +101,7 @@ async function sendClickedActionToModel({ interaction, thread, prompt, }) {
|
|
|
101
101
|
mode: 'opencode',
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
export async function showActionButtons({ thread, sessionId, directory, buttons, }) {
|
|
104
|
+
export async function showActionButtons({ thread, sessionId, directory, buttons, silent, }) {
|
|
105
105
|
const safeButtons = buttons
|
|
106
106
|
.slice(0, 3)
|
|
107
107
|
.map((button) => {
|
|
@@ -145,7 +145,7 @@ export async function showActionButtons({ thread, sessionId, directory, buttons,
|
|
|
145
145
|
const message = await thread.send({
|
|
146
146
|
content: '**Action Required**',
|
|
147
147
|
components: [row],
|
|
148
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
148
|
+
flags: silent ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
|
|
149
149
|
});
|
|
150
150
|
context.messageId = message.id;
|
|
151
151
|
logger.log(`Showed ${safeButtons.length} action button(s) for session ${sessionId}`);
|
|
@@ -9,7 +9,7 @@ import { abbreviatePath } from '../utils.js';
|
|
|
9
9
|
import * as errore from 'errore';
|
|
10
10
|
const logger = createLogger(LogPrefix.ADD_PROJECT);
|
|
11
11
|
export async function handleAddProjectCommand({ command, }) {
|
|
12
|
-
await command.deferReply(
|
|
12
|
+
await command.deferReply();
|
|
13
13
|
const projectId = command.options.getString('project', true);
|
|
14
14
|
const guild = command.guild;
|
|
15
15
|
if (!guild) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// for each question and collects user responses.
|
|
4
4
|
import { StringSelectMenuBuilder, StringSelectMenuInteraction, ActionRowBuilder, MessageFlags, } from 'discord.js';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
|
-
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
6
|
+
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
7
7
|
import { getOpencodeClient } from '../opencode.js';
|
|
8
8
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
9
|
const logger = createLogger(LogPrefix.ASK_QUESTION);
|
|
@@ -15,7 +15,7 @@ export const pendingQuestionContexts = new Map();
|
|
|
15
15
|
* Show dropdown menus for question tool input.
|
|
16
16
|
* Sends one message per question with the dropdown directly under the question text.
|
|
17
17
|
*/
|
|
18
|
-
export async function showAskUserQuestionDropdowns({ thread, sessionId, directory, requestId, input, }) {
|
|
18
|
+
export async function showAskUserQuestionDropdowns({ thread, sessionId, directory, requestId, input, silent, }) {
|
|
19
19
|
const contextHash = crypto.randomBytes(8).toString('hex');
|
|
20
20
|
const context = {
|
|
21
21
|
sessionId,
|
|
@@ -83,7 +83,7 @@ export async function showAskUserQuestionDropdowns({ thread, sessionId, director
|
|
|
83
83
|
await thread.send({
|
|
84
84
|
content: `**${(q.header || '').slice(0, 200)}**\n${q.question.slice(0, 1700)}`,
|
|
85
85
|
components: [actionRow],
|
|
86
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
86
|
+
flags: silent ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
logger.log(`Showed ${input.questions.length} question dropdown(s) for session ${sessionId}`);
|
|
@@ -75,7 +75,7 @@ export async function handleContextUsageCommand({ command, }) {
|
|
|
75
75
|
if (m.info.role !== 'assistant') {
|
|
76
76
|
return false;
|
|
77
77
|
}
|
|
78
|
-
if (!
|
|
78
|
+
if (!m.info.tokens) {
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
return getTokenTotal(m.info.tokens) > 0;
|
|
@@ -58,7 +58,7 @@ export async function createNewProject({ guild, projectName, botName, }) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
export async function handleCreateNewProjectCommand({ command, appId, }) {
|
|
61
|
-
await command.deferReply(
|
|
61
|
+
await command.deferReply();
|
|
62
62
|
const projectName = command.options.getString('name', true);
|
|
63
63
|
const guild = command.guild;
|
|
64
64
|
const channel = command.channel;
|
package/dist/commands/fork.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectM
|
|
|
3
3
|
import { getThreadSession, setThreadSession, setPartMessagesBatch, } from '../database.js';
|
|
4
4
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, resolveTextChannel, sendThreadMessage, } from '../discord-utils.js';
|
|
6
|
-
import {
|
|
6
|
+
import { collectSessionChunks, batchChunksForDiscord } from '../message-formatting.js';
|
|
7
7
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
8
8
|
import * as errore from 'errore';
|
|
9
9
|
const sessionLogger = createLogger(LogPrefix.SESSION);
|
|
@@ -135,7 +135,7 @@ export async function handleForkSelectMenu(interaction) {
|
|
|
135
135
|
});
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
|
-
await interaction.deferReply(
|
|
138
|
+
await interaction.deferReply();
|
|
139
139
|
const threadChannel = interaction.channel;
|
|
140
140
|
if (!threadChannel) {
|
|
141
141
|
await interaction.editReply('Could not access thread channel');
|
|
@@ -184,9 +184,11 @@ export async function handleForkSelectMenu(interaction) {
|
|
|
184
184
|
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
185
185
|
reason: `Forked from session ${sessionId}`,
|
|
186
186
|
});
|
|
187
|
+
// Claim the forked session immediately so external polling does not race
|
|
188
|
+
// and create a duplicate Sync thread before the rest of this setup runs.
|
|
189
|
+
await setThreadSession(thread.id, forkedSession.id);
|
|
187
190
|
// Add user to thread so it appears in their sidebar
|
|
188
191
|
await thread.members.add(interaction.user.id);
|
|
189
|
-
await setThreadSession(thread.id, forkedSession.id);
|
|
190
192
|
sessionLogger.log(`Created forked session ${forkedSession.id} in thread ${thread.id}`);
|
|
191
193
|
await sendThreadMessage(thread, `**Forked session created!**\nFrom: \`${sessionId}\`\nNew session: \`${forkedSession.id}\``);
|
|
192
194
|
// Fetch and display the last assistant messages from the forked session
|
|
@@ -194,13 +196,14 @@ export async function handleForkSelectMenu(interaction) {
|
|
|
194
196
|
sessionID: forkedSession.id,
|
|
195
197
|
});
|
|
196
198
|
if (messagesResponse.data) {
|
|
197
|
-
const {
|
|
199
|
+
const { chunks } = collectSessionChunks({
|
|
198
200
|
messages: messagesResponse.data,
|
|
201
|
+
limit: 30,
|
|
199
202
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await setPartMessagesBatch(partIds.map((partId) => ({
|
|
203
|
+
const batched = batchChunksForDiscord(chunks);
|
|
204
|
+
for (const batch of batched) {
|
|
205
|
+
const discordMessage = await sendThreadMessage(thread, batch.content);
|
|
206
|
+
await setPartMessagesBatch(batch.partIds.map((partId) => ({
|
|
204
207
|
partId,
|
|
205
208
|
messageId: discordMessage.id,
|
|
206
209
|
threadId: thread.id,
|
|
@@ -55,7 +55,7 @@ async function sendPromptToModel({ prompt, thread, projectDirectory, command, ap
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
58
|
-
await command.deferReply(
|
|
58
|
+
await command.deferReply();
|
|
59
59
|
const channel = command.channel;
|
|
60
60
|
if (!channel || !channel.isThread()) {
|
|
61
61
|
await command.editReply('This command can only be used in a thread');
|
|
@@ -11,6 +11,10 @@ import { createWorktreeWithSubmodules, execAsync, listBranchesByLastCommit, vali
|
|
|
11
11
|
import { WORKTREE_PREFIX } from './merge-worktree.js';
|
|
12
12
|
import * as errore from 'errore';
|
|
13
13
|
const logger = createLogger(LogPrefix.WORKTREE);
|
|
14
|
+
/** Status message shown while a worktree is being created. */
|
|
15
|
+
export function worktreeCreatingMessage(worktreeName) {
|
|
16
|
+
return `š³ **Creating worktree: ${worktreeName}**\nā³ Setting up...`;
|
|
17
|
+
}
|
|
14
18
|
class WorktreeError extends Error {
|
|
15
19
|
constructor(message, options) {
|
|
16
20
|
super(message, options);
|
|
@@ -65,37 +69,64 @@ async function getProjectDirectoryFromChannel(channel) {
|
|
|
65
69
|
return channelConfig.directory;
|
|
66
70
|
}
|
|
67
71
|
/**
|
|
68
|
-
* Create worktree
|
|
72
|
+
* Create worktree and update the status message when done.
|
|
73
|
+
* Handles the full lifecycle: pending DB entry, git creation, DB ready/error,
|
|
74
|
+
* tree emoji reaction, and editing the status message.
|
|
75
|
+
*
|
|
76
|
+
* starterMessage is optional ā if omitted, status edits are skipped (creation
|
|
77
|
+
* still proceeds). This keeps worktree creation independent of Discord message
|
|
78
|
+
* delivery, so a transient send failure never silently skips the worktree.
|
|
79
|
+
*
|
|
80
|
+
* Returns the worktree directory on success, or an Error on failure.
|
|
81
|
+
* Never throws ā all internal errors are caught and returned as Error values.
|
|
69
82
|
*/
|
|
70
|
-
async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, baseBranch, rest, }) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
export async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, baseBranch, rest, }) {
|
|
84
|
+
return errore.tryAsync({
|
|
85
|
+
try: async () => {
|
|
86
|
+
logger.log(`Creating worktree "${worktreeName}" for project ${projectDirectory}${baseBranch ? ` from ${baseBranch}` : ''}`);
|
|
87
|
+
await createPendingWorktree({
|
|
88
|
+
threadId: thread.id,
|
|
89
|
+
worktreeName,
|
|
90
|
+
projectDirectory,
|
|
91
|
+
});
|
|
92
|
+
const worktreeResult = await createWorktreeWithSubmodules({
|
|
93
|
+
directory: projectDirectory,
|
|
94
|
+
name: worktreeName,
|
|
95
|
+
baseBranch,
|
|
96
|
+
});
|
|
97
|
+
if (worktreeResult instanceof Error) {
|
|
98
|
+
const errorMsg = worktreeResult.message;
|
|
99
|
+
logger.error('[WORKTREE] Creation failed:', worktreeResult);
|
|
100
|
+
await setWorktreeError({ threadId: thread.id, errorMessage: errorMsg });
|
|
101
|
+
await starterMessage
|
|
102
|
+
?.edit(`š³ **Worktree: ${worktreeName}**\nā ${errorMsg}`)
|
|
103
|
+
.catch(() => { });
|
|
104
|
+
return worktreeResult;
|
|
105
|
+
}
|
|
106
|
+
// Success - update database and edit starter message
|
|
107
|
+
await setWorktreeReady({
|
|
108
|
+
threadId: thread.id,
|
|
109
|
+
worktreeDirectory: worktreeResult.directory,
|
|
110
|
+
});
|
|
111
|
+
// React with tree emoji to mark as worktree thread
|
|
112
|
+
await reactToThread({
|
|
113
|
+
rest,
|
|
114
|
+
threadId: thread.id,
|
|
115
|
+
channelId: thread.parentId || undefined,
|
|
116
|
+
emoji: 'š³',
|
|
117
|
+
});
|
|
118
|
+
await starterMessage
|
|
119
|
+
?.edit(`š³ **Worktree: ${worktreeName}**\n` +
|
|
120
|
+
`š \`${worktreeResult.directory}\`\n` +
|
|
121
|
+
`šæ Branch: \`${worktreeResult.branch}\``)
|
|
122
|
+
.catch(() => { });
|
|
123
|
+
return worktreeResult.directory;
|
|
124
|
+
},
|
|
125
|
+
catch: (e) => {
|
|
126
|
+
logger.error('[WORKTREE] Unexpected error in createWorktreeInBackground:', e);
|
|
127
|
+
return new Error(`Worktree creation failed: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
128
|
+
},
|
|
95
129
|
});
|
|
96
|
-
await starterMessage.edit(`š³ **Worktree: ${worktreeName}**\n` +
|
|
97
|
-
`š \`${worktreeResult.directory}\`\n` +
|
|
98
|
-
`šæ Branch: \`${worktreeResult.branch}\``);
|
|
99
130
|
}
|
|
100
131
|
async function findExistingWorktreePath({ projectDirectory, worktreeName, }) {
|
|
101
132
|
const listResult = await errore.tryAsync({
|
|
@@ -121,7 +152,7 @@ async function findExistingWorktreePath({ projectDirectory, worktreeName, }) {
|
|
|
121
152
|
return undefined;
|
|
122
153
|
}
|
|
123
154
|
export async function handleNewWorktreeCommand({ command, }) {
|
|
124
|
-
await command.deferReply(
|
|
155
|
+
await command.deferReply();
|
|
125
156
|
const channel = command.channel;
|
|
126
157
|
if (!channel) {
|
|
127
158
|
await command.editReply('Cannot determine channel');
|
|
@@ -187,7 +218,7 @@ export async function handleNewWorktreeCommand({ command, }) {
|
|
|
187
218
|
const result = await errore.tryAsync({
|
|
188
219
|
try: async () => {
|
|
189
220
|
const starterMessage = await textChannel.send({
|
|
190
|
-
content:
|
|
221
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
191
222
|
flags: SILENT_MESSAGE_FLAGS,
|
|
192
223
|
});
|
|
193
224
|
const thread = await starterMessage.startThread({
|
|
@@ -207,12 +238,6 @@ export async function handleNewWorktreeCommand({ command, }) {
|
|
|
207
238
|
return;
|
|
208
239
|
}
|
|
209
240
|
const { thread, starterMessage } = result;
|
|
210
|
-
// Store pending worktree in database
|
|
211
|
-
await createPendingWorktree({
|
|
212
|
-
threadId: thread.id,
|
|
213
|
-
worktreeName,
|
|
214
|
-
projectDirectory,
|
|
215
|
-
});
|
|
216
241
|
await command.editReply(`Creating worktree in ${thread.toString()}`);
|
|
217
242
|
// Create worktree in background (don't await)
|
|
218
243
|
createWorktreeInBackground({
|
|
@@ -282,15 +307,9 @@ async function handleWorktreeInThread({ command, thread, }) {
|
|
|
282
307
|
await command.editReply(`Worktree \`${worktreeName}\` already exists at \`${existingWorktreePath}\``);
|
|
283
308
|
return;
|
|
284
309
|
}
|
|
285
|
-
// Store pending worktree in database for this existing thread
|
|
286
|
-
await createPendingWorktree({
|
|
287
|
-
threadId: thread.id,
|
|
288
|
-
worktreeName,
|
|
289
|
-
projectDirectory,
|
|
290
|
-
});
|
|
291
310
|
// Send status message in thread
|
|
292
311
|
const statusMessage = await thread.send({
|
|
293
|
-
content:
|
|
312
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
294
313
|
flags: SILENT_MESSAGE_FLAGS,
|
|
295
314
|
});
|
|
296
315
|
await command.editReply(`Creating worktree \`${worktreeName}\` for this thread...`);
|
|
@@ -6,7 +6,7 @@ import { createLogger, LogPrefix } from '../logger.js';
|
|
|
6
6
|
import { abbreviatePath } from '../utils.js';
|
|
7
7
|
const logger = createLogger(LogPrefix.REMOVE_PROJECT);
|
|
8
8
|
export async function handleRemoveProjectCommand({ command, appId, }) {
|
|
9
|
-
await command.deferReply(
|
|
9
|
+
await command.deferReply();
|
|
10
10
|
const directory = command.options.getString('project', true);
|
|
11
11
|
const guild = command.guild;
|
|
12
12
|
if (!guild) {
|
package/dist/commands/resume.js
CHANGED
|
@@ -4,12 +4,12 @@ import fs from 'node:fs';
|
|
|
4
4
|
import { getChannelDirectory, setThreadSession, setPartMessagesBatch, getAllThreadSessionIds, } from '../database.js';
|
|
5
5
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
6
|
import { sendThreadMessage, resolveProjectDirectoryFromAutocomplete, NOTIFY_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { collectSessionChunks, batchChunksForDiscord } from '../message-formatting.js';
|
|
8
8
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
9
|
import * as errore from 'errore';
|
|
10
10
|
const logger = createLogger(LogPrefix.RESUME);
|
|
11
11
|
export async function handleResumeCommand({ command, }) {
|
|
12
|
-
await command.deferReply(
|
|
12
|
+
await command.deferReply();
|
|
13
13
|
const sessionId = command.options.getString('session', true);
|
|
14
14
|
const channel = command.channel;
|
|
15
15
|
const isThread = channel &&
|
|
@@ -56,9 +56,11 @@ export async function handleResumeCommand({ command, }) {
|
|
|
56
56
|
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
57
57
|
reason: `Resuming session ${sessionId}`,
|
|
58
58
|
});
|
|
59
|
+
// Claim the resumed session immediately so external polling does not race
|
|
60
|
+
// and create a duplicate Sync thread before the rest of this setup runs.
|
|
61
|
+
await setThreadSession(thread.id, sessionId);
|
|
59
62
|
// Add user to thread so it appears in their sidebar
|
|
60
63
|
await thread.members.add(command.user.id);
|
|
61
|
-
await setThreadSession(thread.id, sessionId);
|
|
62
64
|
logger.log(`[RESUME] Created thread ${thread.id} for session ${sessionId}`);
|
|
63
65
|
const messagesResponse = await getClient().session.messages({
|
|
64
66
|
sessionID: sessionId,
|
|
@@ -70,16 +72,17 @@ export async function handleResumeCommand({ command, }) {
|
|
|
70
72
|
await command.editReply(`Resumed session "${sessionTitle}" in ${thread.toString()}`);
|
|
71
73
|
await sendThreadMessage(thread, `**Resumed session:** ${sessionTitle}\n**Created:** ${new Date(sessionResponse.data.time.created).toLocaleString()}\n\n*Loading ${messages.length} messages...*`);
|
|
72
74
|
try {
|
|
73
|
-
const {
|
|
75
|
+
const { chunks, skippedCount } = collectSessionChunks({
|
|
74
76
|
messages,
|
|
77
|
+
limit: 30,
|
|
75
78
|
});
|
|
76
79
|
if (skippedCount > 0) {
|
|
77
80
|
await sendThreadMessage(thread, `*Skipped ${skippedCount} older assistant parts...*`);
|
|
78
81
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
await setPartMessagesBatch(partIds.map((partId) => ({
|
|
82
|
+
const batched = batchChunksForDiscord(chunks);
|
|
83
|
+
for (const batch of batched) {
|
|
84
|
+
const discordMessage = await sendThreadMessage(thread, batch.content);
|
|
85
|
+
await setPartMessagesBatch(batch.partIds.map((partId) => ({
|
|
83
86
|
partId,
|
|
84
87
|
messageId: discordMessage.id,
|
|
85
88
|
threadId: thread.id,
|
|
@@ -15,11 +15,14 @@ import { startWebsockify } from '../websockify.js';
|
|
|
15
15
|
import { createLogger } from '../logger.js';
|
|
16
16
|
import { execAsync } from '../worktrees.js';
|
|
17
17
|
const logger = createLogger('SCREEN');
|
|
18
|
+
const SECURE_REPLY_FLAGS = MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS;
|
|
18
19
|
/** One active screenshare per guild (Discord) or per machine (CLI) */
|
|
19
20
|
const activeSessions = new Map();
|
|
20
21
|
const VNC_PORT = 5900;
|
|
21
|
-
const
|
|
22
|
+
const MAX_SESSION_MINUTES = 30;
|
|
23
|
+
const MAX_SESSION_MS = MAX_SESSION_MINUTES * 60 * 1000;
|
|
22
24
|
const TUNNEL_BASE_DOMAIN = 'kimaki.xyz';
|
|
25
|
+
const SCREENSHARE_TUNNEL_ID_BYTES = 16;
|
|
23
26
|
// Public noVNC client ā we point it at our tunnel URL
|
|
24
27
|
export function buildNoVncUrl({ tunnelHost }) {
|
|
25
28
|
const params = new URLSearchParams({
|
|
@@ -32,6 +35,9 @@ export function buildNoVncUrl({ tunnelHost }) {
|
|
|
32
35
|
});
|
|
33
36
|
return `https://novnc.com/noVNC/vnc.html?${params.toString()}`;
|
|
34
37
|
}
|
|
38
|
+
export function createScreenshareTunnelId() {
|
|
39
|
+
return crypto.randomBytes(SCREENSHARE_TUNNEL_ID_BYTES).toString('hex');
|
|
40
|
+
}
|
|
35
41
|
// macOS has two separate services:
|
|
36
42
|
// - "Screen Sharing" = view-only VNC (com.apple.screensharing)
|
|
37
43
|
// - "Remote Management" = full control VNC with mouse/keyboard (ARDAgent)
|
|
@@ -170,7 +176,7 @@ export async function startScreenshare({ sessionKey, startedBy, }) {
|
|
|
170
176
|
throw err;
|
|
171
177
|
}
|
|
172
178
|
// Step 3: create tunnel
|
|
173
|
-
const tunnelId =
|
|
179
|
+
const tunnelId = createScreenshareTunnelId();
|
|
174
180
|
const tunnelClient = new TunnelClient({
|
|
175
181
|
localPort: wsInstance.port,
|
|
176
182
|
tunnelId,
|
|
@@ -197,9 +203,9 @@ export async function startScreenshare({ sessionKey, startedBy, }) {
|
|
|
197
203
|
const tunnelHost = `${tunnelId}-tunnel.${TUNNEL_BASE_DOMAIN}`;
|
|
198
204
|
const tunnelUrl = `https://${tunnelHost}`;
|
|
199
205
|
const noVncUrl = buildNoVncUrl({ tunnelHost });
|
|
200
|
-
// Auto-kill after
|
|
206
|
+
// Auto-kill after a short session so a leaked URL does not stay usable all day.
|
|
201
207
|
const timeoutTimer = setTimeout(() => {
|
|
202
|
-
logger.log(`Screen share auto-stopped after
|
|
208
|
+
logger.log(`Screen share auto-stopped after ${MAX_SESSION_MINUTES} minutes (key: ${sessionKey})`);
|
|
203
209
|
stopScreenshare({ sessionKey });
|
|
204
210
|
}, MAX_SESSION_MS);
|
|
205
211
|
// Don't keep the process alive just for this timer
|
|
@@ -240,14 +246,16 @@ export async function handleScreenshareCommand({ command, }) {
|
|
|
240
246
|
});
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
|
-
await command.deferReply({ flags:
|
|
249
|
+
await command.deferReply({ flags: SECURE_REPLY_FLAGS });
|
|
244
250
|
try {
|
|
245
251
|
const session = await startScreenshare({
|
|
246
252
|
sessionKey: guildId,
|
|
247
253
|
startedBy: command.user.tag,
|
|
248
254
|
});
|
|
249
255
|
await command.editReply({
|
|
250
|
-
content: `Screen sharing started
|
|
256
|
+
content: `Screen sharing started. This reply is private and the URL uses a high-entropy tunnel id. ` +
|
|
257
|
+
`It will auto-stop after ${MAX_SESSION_MINUTES} minutes. Use /screenshare-stop to stop sooner.\n` +
|
|
258
|
+
`${session.noVncUrl}`,
|
|
251
259
|
});
|
|
252
260
|
}
|
|
253
261
|
catch (err) {
|