kimaki 1.0.0 → 1.1.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.
Files changed (94) hide show
  1. package/dist/autostart.js +15 -3
  2. package/dist/autostart.js.map +1 -1
  3. package/dist/bot.d.ts +4 -3
  4. package/dist/bot.js.map +1 -1
  5. package/dist/builtin-skills.d.ts +18 -0
  6. package/dist/builtin-skills.js +51 -0
  7. package/dist/builtin-skills.js.map +1 -0
  8. package/dist/builtin-skills.test.d.ts +1 -0
  9. package/dist/builtin-skills.test.js +28 -0
  10. package/dist/builtin-skills.test.js.map +1 -0
  11. package/dist/cli/bot.js +7 -0
  12. package/dist/cli/bot.js.map +1 -1
  13. package/dist/cli/discord.js +1 -1
  14. package/dist/cli/discord.js.map +1 -1
  15. package/dist/cli/session.js +17 -53
  16. package/dist/cli/session.js.map +1 -1
  17. package/dist/cli-discord.e2e.test.js +19 -1
  18. package/dist/cli-discord.e2e.test.js.map +1 -1
  19. package/dist/cli-lock.e2e.test.js +6 -6
  20. package/dist/cli-read.e2e.test.js +8 -3
  21. package/dist/cli-read.e2e.test.js.map +1 -1
  22. package/dist/commands/preference-commands.js +17 -3
  23. package/dist/commands/preference-commands.js.map +1 -1
  24. package/dist/commands/worktree-commands.d.ts +1 -1
  25. package/dist/effects.js +30 -6
  26. package/dist/effects.js.map +1 -1
  27. package/dist/errors.d.ts +1 -1
  28. package/dist/errors.js +1 -1
  29. package/dist/errors.js.map +1 -1
  30. package/dist/event-loop.d.ts +2 -1
  31. package/dist/event-loop.js +5 -1
  32. package/dist/event-loop.js.map +1 -1
  33. package/dist/lock-routes.d.ts +15 -15
  34. package/dist/login.e2e.test.js +2 -2
  35. package/dist/main.d.ts +1 -0
  36. package/dist/main.js +222 -151
  37. package/dist/main.js.map +1 -1
  38. package/dist/opencode-server.js +15 -11
  39. package/dist/opencode-server.js.map +1 -1
  40. package/dist/opencode-server.test.js +1 -1
  41. package/dist/opencode-server.test.js.map +1 -1
  42. package/dist/plugin/index.js +19 -1
  43. package/dist/plugin/index.js.map +1 -1
  44. package/dist/plugin/read-media.d.ts +23 -0
  45. package/dist/plugin/read-media.js +72 -0
  46. package/dist/plugin/read-media.js.map +1 -0
  47. package/dist/plugin.e2e.test.js +6 -1
  48. package/dist/plugin.e2e.test.js.map +1 -1
  49. package/dist/prompt.js +10 -11
  50. package/dist/prompt.js.map +1 -1
  51. package/dist/session-events.d.ts +28 -0
  52. package/dist/session-events.js +70 -0
  53. package/dist/session-events.js.map +1 -1
  54. package/dist/sessions.d.ts +5 -5
  55. package/dist/sessions.js +3 -2
  56. package/dist/sessions.js.map +1 -1
  57. package/dist/slash-commands.js +3 -2
  58. package/dist/slash-commands.js.map +1 -1
  59. package/dist/system-prompt.d.ts +13 -2
  60. package/dist/system-prompt.js +41 -64
  61. package/dist/system-prompt.js.map +1 -1
  62. package/dist/voice-attachments.e2e.test.js +6 -3
  63. package/dist/voice-attachments.e2e.test.js.map +1 -1
  64. package/package.json +7 -6
  65. package/skills/kimaki-debug/SKILL.md +44 -0
  66. package/skills/kimaki-tunnel/SKILL.md +34 -0
  67. package/src/autostart.ts +15 -3
  68. package/src/bot.ts +2 -0
  69. package/src/builtin-skills.test.ts +29 -0
  70. package/src/builtin-skills.ts +59 -0
  71. package/src/cli/bot.ts +6 -0
  72. package/src/cli/discord.ts +1 -1
  73. package/src/cli/session.ts +14 -47
  74. package/src/cli-discord.e2e.test.ts +18 -1
  75. package/src/cli-lock.e2e.test.ts +6 -6
  76. package/src/cli-read.e2e.test.ts +8 -3
  77. package/src/cli-sessions.e2e.test.ts +2 -4
  78. package/src/commands/preference-commands.ts +19 -6
  79. package/src/effects.ts +40 -6
  80. package/src/errors.ts +1 -1
  81. package/src/event-loop.ts +9 -0
  82. package/src/login.e2e.test.ts +2 -2
  83. package/src/main.ts +10 -2
  84. package/src/opencode-server.test.ts +1 -1
  85. package/src/opencode-server.ts +12 -9
  86. package/src/plugin/index.ts +20 -1
  87. package/src/plugin.e2e.test.ts +6 -1
  88. package/src/prompt.ts +10 -10
  89. package/src/session-events.ts +83 -0
  90. package/src/sessions.ts +3 -2
  91. package/src/slash-commands.ts +3 -2
  92. package/src/system-prompt.ts +43 -64
  93. package/src/voice-attachments.e2e.test.ts +6 -3
  94. package/src/plugin/session-id/index.ts +0 -19
@@ -15,8 +15,11 @@ const matchers = [
15
15
  {
16
16
  id: 'image',
17
17
  priority: 500,
18
- // The plugin adds <local-files> and <discord-user> to the model request only.
19
- when: { latestUserTextRegex: 'image-marker[\\s\\S]*<local-files>[\\s\\S]*archive\\.zip\\n</local-files>\\n<discord-user ', rawPromptIncludes: 'image/png' },
18
+ // The plugin adds the numbered <attachments> and <discord-user> to the model request only.
19
+ when: {
20
+ latestUserTextRegex: 'image-marker[\\s\\S]*<attachments>[\\s\\S]*1\\. dot\\.png \\(inline\\): [^\\n]*\\n2\\. notes\\.txt: [^\\n]*\\n3\\. archive\\.zip: [^\\n]*\\n</attachments>\\n<discord-user ',
21
+ rawPromptIncludes: 'image/png',
22
+ },
20
23
  then: { parts: textParts('I see an image') },
21
24
  },
22
25
  reply('voice-steer', 'voice steer ok'),
@@ -106,7 +109,7 @@ test('image and text attachments reach the model as files', async () => {
106
109
  // OpenCode drops binary files: the model gets their local paths from metadata, the stored text stays clean.
107
110
  expect(user?.type === 'user' && user.text).toBe('What is this image-marker');
108
111
  const discord = user?.type === 'user' ? user.metadata?.['discord'] : null;
109
- expect(JSON.stringify(discord).replaceAll(dataDir, '<data>').replace(/\/\d+\//g, '/<message>/').replace(/\d{6,}/g, 'ID')).toMatchInlineSnapshot(`"{"userId":"ID","username":"tommy","messageId":"ID","threadId":"ID","threadName":"What is this image-marker","localFiles":["<data>/attachments/<message>/1-notes.txt","<data>/attachments/<message>/2-archive.zip"]}"`);
112
+ expect(JSON.stringify(discord).replaceAll(dataDir, '<data>').replace(/\/\d+\//g, '/<message>/').replace(/\d{6,}/g, 'ID')).toMatchInlineSnapshot(`"{"userId":"ID","username":"tommy","messageId":"ID","threadId":"ID","threadName":"What is this image-marker","attachments":[{"name":"dot.png","path":"<data>/attachments/<message>/0-dot.png","inline":true},{"name":"notes.txt","path":"<data>/attachments/<message>/1-notes.txt","inline":false},{"name":"archive.zip","path":"<data>/attachments/<message>/2-archive.zip","inline":false}]}"`);
110
113
  });
111
114
  test('voice in a channel starts a session with the transcription and the spoken agent', async () => {
112
115
  const thread = await newThread(() => twin.discord
@@ -1 +1 @@
1
- {"version":3,"file":"voice-attachments.e2e.test.js","sourceRoot":"","sources":["../src/voice-attachments.e2e.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"voice-attachments.e2e.test.js","sourceRoot":"","sources":["../src/voice-attachments.e2e.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kimaki",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A collaborative agent orchestrator for OpenCode, inside Discord",
5
5
  "repository": "https://github.com/remorses/kimaki",
6
6
  "type": "module",
@@ -10,21 +10,22 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "!dist/.tsbuildinfo",
13
- "src"
13
+ "src",
14
+ "skills"
14
15
  ],
15
16
  "dependencies": {
16
17
  "@clack/prompts": "^1.7.0",
17
18
  "@discordjs/core": "2.5.0",
18
19
  "@discordjs/voice": "^0.19.2",
19
20
  "@libsql/client": "^0.17.2",
20
- "@opencode/client": "2.0.19",
21
- "@opencode/plugin": "2.0.19",
21
+ "@opencode/client": "2.0.29",
22
+ "@opencode/plugin": "2.0.29",
22
23
  "@strada.sh/sdk": "^0.8.0",
23
24
  "cron-parser": "^5.5.0",
24
25
  "discord-api-types": "^0.38.40",
25
26
  "discord.js": "^14.26.3",
26
27
  "drizzle-orm": "1.0.0-beta.22",
27
- "effect": "4.0.0-rc.112",
28
+ "effect": "4.0.1",
28
29
  "eventsource-parser": "^3.0.6",
29
30
  "goke": "^6.15.1",
30
31
  "immer": "^11.1.18",
@@ -42,7 +43,7 @@
42
43
  "traforo": "^0.7.2"
43
44
  },
44
45
  "devDependencies": {
45
- "@opencode/cli": "2.0.19",
46
+ "@opencode/cli": "2.0.29",
46
47
  "@types/mdast": "^4.0.4",
47
48
  "@types/node": "^24.3.0",
48
49
  "@types/ws": "^8.5.0",
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: kimaki-debug
3
+ description: Debug and administer Kimaki itself. Use when sessions do not respond, the bot shows errors, you need the Kimaki or OpenCode service logs, want to profile the bot, restart it (only when asked), connect a model provider with `kimaki login`, check who may use the bot, or file a Kimaki bug report. ALWAYS load when Kimaki misbehaves or before filing a Kimaki issue. Only for Kimaki Discord sessions.
4
+ ---
5
+
6
+ # Debugging Kimaki
7
+
8
+ Read the logs before guessing. Do not restart the bot (`kimaki restart`) unless the user explicitly asks.
9
+
10
+ ## Kimaki bot log
11
+
12
+ `$KIMAKI_DATA_DIR/kimaki.log` holds only the current bot run. The run before the last restart (with the crash that caused it) is in `kimaki.previous.log`. Lines are `<ISO time> <LEVEL> [<MODULE>] <message>` and errors include their cause chain. It logs incoming messages, run start/finish/failure per thread and session, Discord and OpenCode errors, routines and crashes.
13
+
14
+ ```bash
15
+ tail -n 200 $KIMAKI_DATA_DIR/kimaki.log
16
+ grep -E ' (WARN|ERROR) ' $KIMAKI_DATA_DIR/kimaki.log | tail -n 50
17
+ grep '<thread_id or ses_id>' $KIMAKI_DATA_DIR/kimaki.log
18
+ ```
19
+
20
+ ## OpenCode service log
21
+
22
+ The shared `opencode serve --service` daemon appends to `~/.local/share/opencode/log/opencode.log` (`$XDG_DATA_HOME/opencode/log`; `opencode debug paths` prints the folder). The file is never reset and can be very large: use `tail` or `grep`, never read it whole. Lines are logfmt. `run=<id>` is one daemon process, so filter by the run of the last line. The daemon pid and URL are in `~/.local/state/opencode/service.json`.
23
+
24
+ ```bash
25
+ LOG=~/.local/share/opencode/log/opencode.log
26
+ RUN=$(tail -n 1 $LOG | grep -o 'run=[a-z0-9]*')
27
+ grep "$RUN " $LOG | grep -E '^timestamp=[^ ]+ level=(ERROR|WARN)' | tail -n 50
28
+ grep "$RUN " $LOG | grep '<ses_id>' | tail -n 100
29
+ ```
30
+
31
+ ## Other tools
32
+
33
+ - `kimaki session events <id>` prints the recorded OpenCode events of a thread as JSONL.
34
+ - `kimaki restart` restarts the bot (only when asked). Sessions keep running in OpenCode.
35
+ - `kimaki profile cpu --duration 30s` and `kimaki profile heap` print the profile file path.
36
+ - `kimaki login PROVIDER` lists login methods of a provider. `--key` connects an API key, `--method` starts OAuth, `--attempt` checks or completes an attempt. Credentials belong to OpenCode.
37
+
38
+ ## Who can use the bot
39
+
40
+ Only users with one of these Discord permissions can message the bot: Server Owner, Administrator, Manage Server, or the "Kimaki" role (case-insensitive). Other Discord bots are ignored; assign a bot the "Kimaki" role to let it trigger sessions (multi-agent orchestration).
41
+
42
+ ## Reporting a bug
43
+
44
+ ALWAYS read https://kimaki.dev/docs/guides/report-bugs first. That page is the source of truth for exporting session JSONL, sharing evidence in a gist and filing bugs. Never open a pull request on remorses/kimaki unless remorses asked for one in a comment on the issue.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: kimaki-tunnel
3
+ description: Share a local dev server with the user through a public URL. Use when the user should open a localhost app in a browser, test a webhook or OAuth callback against a local server, or preview a dev server from Discord. Runs `kimaki tunnel` in a named background tuistory session. ALWAYS load before starting a dev server the user must open. Only for Kimaki Discord sessions.
4
+ ---
5
+
6
+ # Dev servers with tunnel access
7
+
8
+ Localhost URLs are useless from Discord. Wrap the dev server in `kimaki tunnel` to get a public URL, and run it in a named background `tuistory` session so you can wait for output, read logs and stop it later. Name the session descriptively (e.g. `projectname-dev`) and reuse it. Run `tuistory --help` first (or `bunx tuistory` when it is not installed). Invoke `kimaki` directly, not via `npx` or `bunx`.
9
+
10
+ - Use random tunnel IDs by default. Pass `-t <id>` only for services that are safe to be publicly discoverable.
11
+ - `kimaki tunnel` detects the local port from the child output. Pass `--port` only when the server prints no detectable localhost URL or port line.
12
+ - `kimaki tunnel` injects `TRAFORO_URL` into the child process. Wire the app to it so OAuth callbacks, webhook URLs and absolute links use the public URL.
13
+
14
+ ```bash
15
+ # start in a named background session, wait for output, then read the tunnel URL
16
+ tuistory launch "kimaki tunnel -- pnpm dev" -s myapp-dev
17
+ tuistory -s myapp-dev wait "/ready|local|tunnel/i" --timeout 30000
18
+ tuistory read -s myapp-dev
19
+
20
+ # pass the public URL to the app (better-auth, Next.js, Vite; node reads process.env.TRAFORO_URL)
21
+ tuistory launch "kimaki tunnel -- sh -c 'BETTER_AUTH_URL=$TRAFORO_URL exec pnpm dev'" -s myapp-dev
22
+ tuistory launch "kimaki tunnel -- sh -c 'APP_URL=$TRAFORO_URL exec pnpm dev'" -s myapp-dev
23
+ tuistory launch "kimaki tunnel -- sh -c 'VITE_BASE_URL=$TRAFORO_URL exec pnpm dev'" -s myapp-dev
24
+
25
+ # custom tunnel ID (only for intentionally public-safe services)
26
+ tuistory launch "kimaki tunnel -t holocron -- pnpm dev" -s holocron-dev
27
+
28
+ # list sessions; stop with Ctrl+C, then close
29
+ tuistory sessions
30
+ tuistory -s myapp-dev press ctrl c
31
+ tuistory -s myapp-dev close
32
+ ```
33
+
34
+ Share the printed public URL with the user as a clickable link. Stop the session when the user is done.
package/src/autostart.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // on every start; the Kimaki OpenCode plugin opens it when no bot answers on
3
3
  // the lock port:
4
4
  //
5
- // kimaki start ──writes──▶ ~/.kimaki/autostart.command (exec node <pkg>/dist/cli.js ...)
5
+ // kimaki start ──writes──▶ ~/.kimaki/autostart.command (exec <bot node> <pkg>/dist/cli.js ...)
6
6
  // ▲
7
7
  // OpenCode loads plugin ──no /health on 29988──▶ open -g autostart.command
8
8
  // (default terminal app, in background)
@@ -37,13 +37,25 @@ export async function writeAutostartScript({ dataDir, args }: { dataDir: string;
37
37
  const file = path.join(dataDir, SCRIPT_NAME)
38
38
  const cli = distCliEntry()
39
39
  if (!fs.existsSync(cli)) return fs.promises.rm(file, { force: true }).catch((cause: Error) => cause)
40
- const command = [process.execPath, cli, '--if-not-running', '--data-dir', dataDir, ...args].map(quote).join(' ')
40
+ const command = [cli, '--if-not-running', '--data-dir', dataDir, ...args].map(quote).join(' ')
41
+ // OpenCode is a compiled Bun binary with no Node inside, so reuse the Node that ran
42
+ // this bot. Fall back to PATH when a version manager removed that Node.
41
43
  // The bot inherits the OpenCode (or Terminal) env: drop overrides that would change its port, token or service.
42
44
  const script = dedent`
43
45
  #!/bin/sh
44
46
  # Generated by Kimaki on bot start. The Kimaki OpenCode plugin opens it when no bot runs.
45
47
  unset KIMAKI_LOCK_PORT KIMAKI_BOT_TOKEN KIMAKI_OPENCODE_SERVICE_FILE KIMAKI_DATA_DIR KIMAKI_SUPERVISED
46
- exec ${command}
48
+ node=${quote(process.execPath)}
49
+ [ -x "$node" ] || node=node
50
+ echo 'Kimaki autostart'
51
+ echo 'OpenCode started and no Kimaki bot answered on port ${DEFAULT_LOCK_PORT}, so the Kimaki'
52
+ echo 'OpenCode plugin opened this window to start the Discord bot.'
53
+ echo 'The bot lets you talk to your OpenCode sessions from Discord.'
54
+ echo 'Close this window or press Ctrl+C to stop the bot.'
55
+ echo "Node: $node"
56
+ echo "Script: $0"
57
+ echo
58
+ exec "$node" ${command}
47
59
  ` + '\n'
48
60
  return fs.promises.writeFile(file, script, { mode: 0o755 }).then(() => fs.promises.chmod(file, 0o755))
49
61
  .catch((cause: Error) => cause)
package/src/bot.ts CHANGED
@@ -65,6 +65,8 @@ export type Bot = {
65
65
  realtimeBaseUrls: RealtimeBaseUrls
66
66
  // Default for channels without a channel_worktrees row.
67
67
  autoWorktrees: boolean
68
+ // Built-in skill ids (builtin-skills.ts) denied through the permissions of new sessions.
69
+ disabledSkills: readonly string[]
68
70
  features: BotFeatures
69
71
  }
70
72
 
@@ -0,0 +1,29 @@
1
+ import { expect, test } from 'vitest'
2
+
3
+ import { BUILTIN_SKILL_IDS, disabledSkillRules, parseDisabledSkills, readBuiltinSkills } from './builtin-skills.ts'
4
+
5
+ test('every built-in skill file parses', async () => {
6
+ const skills = await readBuiltinSkills()
7
+ if (skills instanceof Error) throw skills
8
+ expect(skills.map((skill) => skill.id)).toEqual([...BUILTIN_SKILL_IDS])
9
+ for (const skill of skills) {
10
+ expect(skill.description, skill.id).toContain('Only for Kimaki Discord sessions')
11
+ expect(skill.content.startsWith('# '), skill.id).toBe(true)
12
+ }
13
+ })
14
+
15
+ test('--disable-skills accepts all, a list, and rejects unknown ids', () => {
16
+ expect(parseDisabledSkills(undefined)).toEqual([])
17
+ expect(parseDisabledSkills('kimaki-tunnel, kimaki-debug')).toEqual(['kimaki-tunnel', 'kimaki-debug'])
18
+ expect(parseDisabledSkills('all')).toEqual([...BUILTIN_SKILL_IDS])
19
+ expect(parseDisabledSkills('tunnel')).toBeInstanceOf(Error)
20
+ expect(disabledSkillRules(['kimaki-tunnel'])).toMatchInlineSnapshot(`
21
+ [
22
+ {
23
+ "action": "skill",
24
+ "effect": "deny",
25
+ "resource": "kimaki-tunnel",
26
+ },
27
+ ]
28
+ `)
29
+ })
@@ -0,0 +1,59 @@
1
+ // Opt-out skills Kimaki ships in skills/<id>/SKILL.md (debug, tunnel). They are
2
+ // not in the system prompt, which keeps the rest of the CLI reference.
3
+ //
4
+ // plugin (OpenCode process) ──▶ readBuiltinSkills ──▶ ctx.skill.transform(add)
5
+ // bot ──▶ --disable-skills ──▶ session permissions `skill:<id>` deny
6
+ //
7
+ // Plugin files must not pull in discord.js or SQLite: keep this module plain fs.
8
+
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ import { ConfigError, FilesystemError } from './errors.ts'
14
+
15
+ export const BUILTIN_SKILL_IDS = ['kimaki-debug', 'kimaki-tunnel'] as const
16
+
17
+ export type BuiltinSkillId = (typeof BUILTIN_SKILL_IDS)[number]
18
+
19
+ export function isBuiltinSkill(id: string): boolean {
20
+ return (BUILTIN_SKILL_IDS as readonly string[]).includes(id)
21
+ }
22
+
23
+ // src/ and dist/ both sit next to skills/.
24
+ const SKILLS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'skills')
25
+
26
+ export type BuiltinSkill = { id: string; name: string; description: string; path: string; content: string }
27
+
28
+ function frontmatterValue(frontmatter: string, key: string): string | undefined {
29
+ return new RegExp(`^${key}:\\s*(.+)$`, 'm').exec(frontmatter)?.[1]?.trim()
30
+ }
31
+
32
+ export async function readBuiltinSkills(): Promise<FilesystemError | BuiltinSkill[]> {
33
+ const skills: BuiltinSkill[] = []
34
+ for (const id of BUILTIN_SKILL_IDS) {
35
+ const file = path.join(SKILLS_DIR, id, 'SKILL.md')
36
+ const text = await fs.promises.readFile(file, 'utf8').catch((cause) => new FilesystemError({ operation: `read ${file}`, cause }))
37
+ if (text instanceof Error) return text
38
+ const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text)
39
+ const description = match?.[1] ? frontmatterValue(match[1], 'description') : undefined
40
+ if (!match || !description) return new FilesystemError({ operation: `parse ${file}: missing frontmatter description` })
41
+ // Virtual path: OpenCode has no file to scan for these (same as its /builtin skills).
42
+ skills.push({ id, name: id, description, path: `/kimaki/${id}.md`, content: match[2]!.trim() })
43
+ }
44
+ return skills
45
+ }
46
+
47
+ // `all` or a comma separated list of built-in skill ids.
48
+ export function parseDisabledSkills(value: string | undefined): ConfigError | BuiltinSkillId[] {
49
+ const items = (value ?? '').split(',').map((item) => item.trim()).filter(Boolean)
50
+ if (items.includes('all')) return [...BUILTIN_SKILL_IDS]
51
+ const unknown = items.filter((item) => !isBuiltinSkill(item))
52
+ if (unknown.length > 0) return new ConfigError({ reason: `Unknown built-in skill ${unknown.join(', ')}. Choose from: all, ${BUILTIN_SKILL_IDS.join(', ')}` })
53
+ return items as BuiltinSkillId[]
54
+ }
55
+
56
+ // OpenCode hides a skill from the model and the skill tool when its permission is denied.
57
+ export function disabledSkillRules(disabled: readonly string[]) {
58
+ return disabled.map((id) => ({ action: 'skill', resource: id, effect: 'deny' }) as const)
59
+ }
package/src/cli/bot.ts CHANGED
@@ -11,6 +11,7 @@ import dedent from 'string-dedent'
11
11
  import type { Goke } from 'goke'
12
12
 
13
13
  import { distCliEntry, writeAutostartScript } from '../autostart.ts'
14
+ import { parseDisabledSkills } from '../builtin-skills.ts'
14
15
  import { callBot, DEFAULT_LOCK_PORT, RESTART_EXIT_CODE, startLockServer } from '../lock-server.ts'
15
16
  import { parseDuration } from '../duration.ts'
16
17
  import { createLogger, setLogFile } from '../logger.ts'
@@ -88,6 +89,7 @@ export function registerStartCommand(cli: Goke) {
88
89
  .option('--restart-onboarding', 'Choose credentials again')
89
90
  .option('--worktrees', 'Use a fresh Git worktree for new sessions unless the channel overrides it')
90
91
  .option('--voice <name>', 'Voice of voice calls, saved for later starts (e.g. cove, juniper, sol). "default" resets it')
92
+ .option('--disable-skills <list>', 'Built-in Kimaki skills to turn off: "all" or a comma separated list (kimaki-debug,kimaki-tunnel)')
91
93
  .option('--no-analytics', 'Disable anonymous usage analytics (same as KIMAKI_STRADA_ENABLED=0)')
92
94
  .option('--if-not-running', 'Start only if no bot runs; never stop the running one (used by autostart)')
93
95
  .action(async (options) => {
@@ -124,6 +126,8 @@ export function registerStartCommand(cli: Goke) {
124
126
  }
125
127
  const dataDir = dataDirOrDefault(options.dataDir)
126
128
  const urls = gatewayUrlsFromEnv()
129
+ const disabledSkills = parseDisabledSkills(options.disableSkills)
130
+ if (disabledSkills instanceof Error) fail(disabledSkills)
127
131
  const machine = options.machineName ?? defaultMachineName()
128
132
  // Validated before the lock, so a typo never stops the running bot. null: reset to the default.
129
133
  const voice = (() => {
@@ -212,6 +216,7 @@ export function registerStartCommand(cli: Goke) {
212
216
  opencodeDbFile: opencodeDbFile(),
213
217
  analytics: createAnalytics({ dataDir, botMode: credentials.mode, enabled: !options.noAnalytics }),
214
218
  autoWorktrees: Boolean(options.worktrees),
219
+ disabledSkills,
215
220
  })
216
221
  if (bot instanceof Error) failStartup(bot)
217
222
  // exit() keeps process.exitCode: the restart route sets RESTART_EXIT_CODE before its SIGTERM.
@@ -239,6 +244,7 @@ export function registerStartCommand(cli: Goke) {
239
244
  if (lock.port === DEFAULT_LOCK_PORT) {
240
245
  const autostart = await writeAutostartScript({ dataDir, args: [
241
246
  ...(options.worktrees ? ['--worktrees'] : []),
247
+ ...(options.disableSkills ? ['--disable-skills', options.disableSkills] : []),
242
248
  ...(options.noAnalytics ? ['--no-analytics'] : []),
243
249
  ...(options.machineName ? ['--machine-name', options.machineName] : []),
244
250
  ] })
@@ -99,7 +99,7 @@ export function registerDiscordCommands(cli: Goke) {
99
99
  printRows({ json: options.json, rows: users, line: (user) => `${user.id} ${user.username}${user.name ? ` (${user.name})` : ''}` })
100
100
  })
101
101
 
102
- cli.command('upload-to-discord <...files>', 'Attach local files to a session thread or the Kimaki voice channel chat')
102
+ cli.command('upload-to-discord <...files>', 'Attach local files to a session thread or the Kimaki voice channel chat. Pass many files in one call: Discord shows them numbered in argument order')
103
103
  .option('--data-dir <path>', DATA_DIR_HELP)
104
104
  .option('-s, --session <id>', SESSION_HELP)
105
105
  .option('-c, --channel <id>', 'Kimaki voice channel to post in (default in voice calls: KIMAKI_CHANNEL_ID)')
@@ -11,7 +11,7 @@ import { wrapJsonSchema, type Goke } from 'goke'
11
11
  import { parseDuration } from '../duration.ts'
12
12
  import { OpenCodeError } from '../errors.ts'
13
13
  import { editorsForFile, loadFileEditEvents } from '../file-edit-log.ts'
14
- import { allMessages, allSessions, readSessionMarkdown, sessionEventsFile } from '../session-events.ts'
14
+ import { allMessages, allSessions, readSessionMarkdown, sessionEventsFile, syncTranscripts, transcriptsDir } from '../session-events.ts'
15
15
  import {
16
16
  action,
17
17
  DATA_DIR_HELP,
@@ -31,7 +31,7 @@ import {
31
31
 
32
32
  const execFileAsync = promisify(execFile)
33
33
 
34
- // list, search, wait, editors, diff, url
34
+ // list, sync, wait, editors, diff, url
35
35
  export function registerSessionQueryCommands(cli: Goke) {
36
36
  cli.command('session list', 'List sessions with native status and token counts')
37
37
  .option('--data-dir <path>', DATA_DIR_HELP)
@@ -65,54 +65,21 @@ export function registerSessionQueryCommands(cli: Goke) {
65
65
  if (options.active && rows.length === 0) process.exitCode = 1
66
66
  })
67
67
 
68
- cli.command('session search <query>', 'Search titles, then real message content')
68
+ cli.command('session sync', 'Write session transcripts to <data-dir>/transcripts for rg; skips current ones')
69
69
  .option('--data-dir <path>', DATA_DIR_HELP)
70
- .option('--project <path>', 'Project (default: current directory)')
71
- .option('-c, --channel <id>', 'Project of this Discord channel')
72
- .option('--all', 'All projects')
73
- .option('--days <n>', 'Recent days (default: 14; 0 = all)')
70
+ .option('--days <n>', 'Sessions updated in the last n days (default: 14)')
71
+ .option('--all', 'All sessions, any age')
72
+ .option('--rebuild', 'Write transcripts again, also current ones')
74
73
  .option('--json', 'Output as JSON')
75
- .action(async (query, options) => {
76
- const client = await readClient()
77
- const directory = options.all ? undefined : await projectDirectory({ project: options.project, channel: options.channel, dataDir: options.dataDir })
78
- const sessions = await allSessions({ client, directory })
79
- if (sessions instanceof Error) fail(sessions)
80
- // `/pattern/flags` is a regular expression, anything else a case-insensitive substring.
81
- const pattern = query.match(/^\/(.*)\/([dgimsuvy]*)$/)
82
- const expression = pattern ? new RegExp(pattern[1]!, pattern[2]) : null
83
- const matches = (text: string) => {
84
- if (!expression) return text.toLowerCase().includes(query.toLowerCase())
85
- expression.lastIndex = 0
86
- return expression.test(text)
87
- }
88
- const days = Number(options.days ?? 14)
74
+ .action(async (options) => {
75
+ const days = options.all ? 0 : Number(options.days ?? 14)
89
76
  if (!Number.isFinite(days) || days < 0) fail(new Error('--days must be a non-negative number'))
90
- const recent = sessions.filter((session) => !days || session.time.updated >= Date.now() - days * 86400000)
91
- // OpenCode has no content search API: scan pages newest first, stop at the first hit.
92
- const hit = async (session: (typeof sessions)[number]) => {
93
- if (matches(session.title ?? '')) return true
94
- const scan = async (cursor: string | undefined): Promise<OpenCodeError | boolean> => {
95
- const page = await client.message.list({ sessionID: session.id, limit: 200, ...(cursor ? { cursor } : { order: 'desc' as const }) })
96
- .catch((cause) => new OpenCodeError({ operation: 'message.list', cause }))
97
- if (page instanceof Error) return page
98
- if (page.data.some((message) => matches(message.type === 'user' ? message.text : JSON.stringify(message)))) return true
99
- return page.cursor.next ? scan(page.cursor.next) : false
100
- }
101
- return scan(undefined)
102
- }
103
- // 8 sessions at a time.
104
- const results: boolean[] = new Array(recent.length)
105
- const next = { index: 0 }
106
- await Promise.all(Array.from({ length: Math.min(8, recent.length) }, async () => {
107
- while (next.index < recent.length) {
108
- const index = next.index++
109
- const result = await hit(recent[index]!)
110
- if (result instanceof Error) fail(result)
111
- results[index] = result
112
- }
113
- }))
114
- const found = recent.filter((_, index) => results[index])
115
- printRows({ json: options.json, rows: found, line: (session) => `${session.id} ${session.title}` })
77
+ const dataDir = dataDirOrDefault(options.dataDir)
78
+ const result = await syncTranscripts({ client: await readClient(), dataDir, days, rebuild: Boolean(options.rebuild) })
79
+ if (result instanceof Error) fail(result)
80
+ if (result.failed > 0) process.exitCode = 1
81
+ if (options.json) return printJson({ dir: transcriptsDir({ dataDir }), ...result })
82
+ process.stdout.write(`${transcriptsDir({ dataDir })}: ${result.written} written, ${result.skipped} skipped, ${result.failed} failed\n`)
116
83
  })
117
84
 
118
85
  cli.command('session wait <id>', 'Wait until idle or waiting for input, then print the session')
@@ -37,6 +37,10 @@ test('independent Discord CLI lists threads and users, uploads a file, and reads
37
37
  const threads = await cli(['thread', 'list', '--channel', twin.channelId, '--json'])
38
38
  const users = await cli(['user', 'list', '--guild', twin.discord.guildId, '--query', 'tommy', '--json'])
39
39
  await cli(['upload-to-discord', path.join(server.root, 'report.txt'), '--session', started.sessionId])
40
+ // Many files keep argument order, numbered, images and files mixed.
41
+ fs.mkdirSync(path.join(server.root, 'other'), { recursive: true })
42
+ for (const name of ['b.png', 'my shot.png', 'other/b.png']) fs.writeFileSync(path.join(server.root, name), 'png')
43
+ await cli(['upload-to-discord', ...['b.png', 'report.txt', 'my shot.png', 'other/b.png'].map((name) => path.join(server.root, name)), '--session', started.sessionId])
40
44
  // The agent learns that nothing was posted.
41
45
  const missing = await cli(['upload-to-discord', path.join(server.root, 'missing.png'), '--session', started.sessionId]).catch((error: { stderr: string }) => error)
42
46
  expect(missing.stderr.replace(server.root, '<root>')).toMatchInlineSnapshot(`
@@ -51,7 +55,20 @@ test('independent Discord CLI lists threads and users, uploads a file, and reads
51
55
  -# *using deterministic-provider/deterministic-v2 ⋅ build*
52
56
  ok
53
57
  -# *project ⋅ main ⋅ Ns ⋅ deterministic-v2*
54
- [attachment: report.txt]"
58
+ [file: attachment://report.txt]
59
+ [attachment: report.txt]
60
+ -# 1. \`b.png\`
61
+ [media: attachment://b.png]
62
+ -# 2. \`report.txt\`
63
+ [file: attachment://report.txt]
64
+ -# 3. \`my shot.png\`
65
+ [media: attachment://my_shot.png]
66
+ -# 4. \`b.png\`
67
+ [media: attachment://4-b.png]
68
+ [attachment: b.png]
69
+ [attachment: report.txt]
70
+ [attachment: my_shot.png]
71
+ [attachment: 4-b.png]"
55
72
  `)
56
73
  expect(threads.stdout).toContain(started.threadId)
57
74
  expect(users.stdout).toContain(TEST_USER_ID)
@@ -89,12 +89,11 @@ test('CLI help documents the supported P7 commands', async () => {
89
89
  --exclude <id> Exclude session (repeatable)
90
90
  --json Output as JSON
91
91
 
92
- session search <query> Search titles, then real message content
92
+ session sync Write session transcripts to <data-dir>/transcripts for rg; skips current ones
93
93
  --data-dir <path> Data directory (default: ~/.kimaki)
94
- --project <path> Project (default: current directory)
95
- -c, --channel <id> Project of this Discord channel
96
- --all All projects
97
- --days <n> Recent days (default: 14; 0 = all)
94
+ --days <n> Sessions updated in the last n days (default: 14)
95
+ --all All sessions, any age
96
+ --rebuild Write transcripts again, also current ones
98
97
  --json Output as JSON
99
98
 
100
99
  session wait <id> Wait until idle or waiting for input, then print the session
@@ -316,7 +315,7 @@ test('CLI help documents the supported P7 commands', async () => {
316
315
  -q, --query <text> Name filter
317
316
  --json Output as JSON
318
317
 
319
- upload-to-discord <...files> Attach local files to a session thread or the Kimaki voice channel chat
318
+ upload-to-discord <...files> Attach local files to a session thread or the Kimaki voice channel chat. Pass many files in one call: Discord shows them numbered in argument order
320
319
  --data-dir <path> Data directory (default: ~/.kimaki)
321
320
  -s, --session <id> Session ID, Discord thread ID or thread URL (default: OPENCODE_SESSION_ID)
322
321
  -c, --channel <id> Kimaki voice channel to post in (default in voice calls: KIMAKI_CHANNEL_ID)
@@ -377,6 +376,7 @@ test('CLI help documents the supported P7 commands', async () => {
377
376
  --restart-onboarding Choose credentials again
378
377
  --worktrees Use a fresh Git worktree for new sessions unless the channel overrides it
379
378
  --voice <name> Voice of voice calls, saved for later starts (e.g. cove, juniper, sol). "default" resets it
379
+ --disable-skills <list> Built-in Kimaki skills to turn off: "all" or a comma separated list (kimaki-debug,kimaki-tunnel)
380
380
  --no-analytics Disable anonymous usage analytics (same as KIMAKI_STRADA_ENABLED=0)
381
381
  --if-not-running Start only if no bot runs; never stop the running one (used by autostart)
382
382
  -h, --help Display this message
@@ -28,13 +28,15 @@ function cli(args: string[]) {
28
28
  cwd: process.cwd(), env: { ...process.env, KIMAKI_LOCK_PORT: String(bot.lock.port), KIMAKI_OPENCODE_SERVICE_FILE: server.serviceFile, KIMAKI_DISCORD_REST_URL: twin.discord.restUrl },
29
29
  })
30
30
  }
31
- test('session read, list, search, wait, url and events read real session history', async () => {
31
+ test('session read, list, sync, wait, url and events read real session history', async () => {
32
32
  const first = await send(bot, { channelId: twin.channelId, prompt: 'Reader content marker' })
33
33
  if (first instanceof Error) throw first
34
34
  if (!first.sessionId) throw new Error('Expected an AI session')
35
35
  await waitForFooter({ discord: twin.discord, threadId: first.threadId })
36
36
  const list = await cli(['session', 'list', '--project', server.projectDirectory, '--json'])
37
- const search = await cli(['session', 'search', 'Reader content', '--project', server.projectDirectory, '--json'])
37
+ const sync = JSON.parse((await cli(['session', 'sync', '--days', '1', '--json'])).stdout)
38
+ const again = JSON.parse((await cli(['session', 'sync', '--days', '1', '--json'])).stdout)
39
+ const rebuilt = JSON.parse((await cli(['session', 'sync', '--days', '1', '--rebuild', '--json'])).stdout)
38
40
  const read = await cli(['session', 'read', first.threadId])
39
41
  const wait = await cli(['session', 'wait', first.sessionId, '--timeout', '10s'])
40
42
  const url = await cli(['session', 'url', first.sessionId])
@@ -47,7 +49,10 @@ test('session read, list, search, wait, url and events read real session history
47
49
  -# *project ⋅ main ⋅ Ns ⋅ deterministic-v2*"
48
50
  `)
49
51
  expect(list.stdout).toContain(first.sessionId)
50
- expect(search.stdout).toContain(first.sessionId)
52
+ expect(sync.written + sync.skipped).toBeGreaterThan(0)
53
+ expect([again.written, again.failed, rebuilt.skipped, rebuilt.failed]).toEqual([0, 0, 0, 0])
54
+ expect(fs.readFileSync(path.join(dataDir, 'transcripts', `${first.sessionId}.md`), 'utf8')).toContain('## user')
55
+ expect(fs.readFileSync(path.join(dataDir, 'transcripts', `${first.sessionId}.md`), 'utf8')).toContain('Reader content marker')
51
56
  expect(read.stdout).toContain('Reader content marker')
52
57
  expect(wait.stdout).toContain('Reader content marker')
53
58
  expect(url.stdout).toContain(first.threadId)
@@ -1,4 +1,4 @@
1
- // CLI session commands: editors, search by channel, command, named fork, resume by folder.
1
+ // CLI session commands: editors, command, named fork, resume by folder.
2
2
 
3
3
  import fs from 'node:fs'
4
4
  import path from 'node:path'
@@ -68,16 +68,14 @@ test('session diff prints critique output for the session folder', async () => {
68
68
  expect(diff.stdout).toContain('No changes to display')
69
69
  })
70
70
 
71
- test('session editors lists sessions that wrote a file, skips failed tool calls; search accepts a channel', async () => {
71
+ test('session editors lists sessions that wrote a file, skips failed tool calls', async () => {
72
72
  const writer = await newSession('editor-marker write')
73
73
  // An edit of a missing file fails and must not be recorded.
74
74
  await newSession('fail-marker edit')
75
75
  const written = await cli(['session', 'editors', 'edited.txt', '--json'])
76
76
  const failed = await cli(['session', 'editors', path.join(server.projectDirectory, 'missing.txt')]).catch((error: Error & { code: number }) => error)
77
- const searched = await cli(['session', 'search', 'editor-marker', '--channel', twin.channelId])
78
77
  expect(JSON.parse(written.stdout).map((row: { sessionId: string }) => row.sessionId)).toEqual([writer.sessionId])
79
78
  expect(failed instanceof Error && failed.code).toBe(1)
80
- expect(searched.stdout).toContain(writer.sessionId)
81
79
  })
82
80
 
83
81
  test('session command, named fork, and resume without a channel use the session folder', async () => {