@brianli/kimaki 0.4.72-brianli.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/bin.js +2 -0
  2. package/dist/ai-tool-to-genai.js +233 -0
  3. package/dist/ai-tool-to-genai.test.js +267 -0
  4. package/dist/ai-tool.js +6 -0
  5. package/dist/bin.js +87 -0
  6. package/dist/bot-token.js +121 -0
  7. package/dist/bot-token.test.js +134 -0
  8. package/dist/channel-management.js +101 -0
  9. package/dist/cli-parsing.test.js +89 -0
  10. package/dist/cli.js +2529 -0
  11. package/dist/commands/abort.js +82 -0
  12. package/dist/commands/action-buttons.js +257 -0
  13. package/dist/commands/add-project.js +114 -0
  14. package/dist/commands/agent.js +291 -0
  15. package/dist/commands/ask-question.js +223 -0
  16. package/dist/commands/compact.js +120 -0
  17. package/dist/commands/context-usage.js +140 -0
  18. package/dist/commands/create-new-project.js +118 -0
  19. package/dist/commands/diff.js +128 -0
  20. package/dist/commands/file-upload.js +275 -0
  21. package/dist/commands/fork.js +217 -0
  22. package/dist/commands/gemini-apikey.js +70 -0
  23. package/dist/commands/login.js +490 -0
  24. package/dist/commands/mention-mode.js +51 -0
  25. package/dist/commands/merge-worktree.js +124 -0
  26. package/dist/commands/model.js +694 -0
  27. package/dist/commands/permissions.js +163 -0
  28. package/dist/commands/queue.js +217 -0
  29. package/dist/commands/remove-project.js +115 -0
  30. package/dist/commands/restart-opencode-server.js +116 -0
  31. package/dist/commands/resume.js +159 -0
  32. package/dist/commands/run-command.js +79 -0
  33. package/dist/commands/session-id.js +78 -0
  34. package/dist/commands/session.js +192 -0
  35. package/dist/commands/share.js +80 -0
  36. package/dist/commands/types.js +2 -0
  37. package/dist/commands/undo-redo.js +159 -0
  38. package/dist/commands/unset-model.js +152 -0
  39. package/dist/commands/upgrade.js +42 -0
  40. package/dist/commands/user-command.js +148 -0
  41. package/dist/commands/verbosity.js +60 -0
  42. package/dist/commands/worktree-settings.js +50 -0
  43. package/dist/commands/worktree.js +299 -0
  44. package/dist/condense-memory.js +33 -0
  45. package/dist/config.js +110 -0
  46. package/dist/database.js +1050 -0
  47. package/dist/db.js +159 -0
  48. package/dist/db.test.js +49 -0
  49. package/dist/discord-api.js +28 -0
  50. package/dist/discord-auth.js +231 -0
  51. package/dist/discord-auth.test.js +80 -0
  52. package/dist/discord-bot.js +997 -0
  53. package/dist/discord-utils.js +560 -0
  54. package/dist/discord-utils.test.js +115 -0
  55. package/dist/errors.js +167 -0
  56. package/dist/escape-backticks.test.js +429 -0
  57. package/dist/format-tables.js +122 -0
  58. package/dist/format-tables.test.js +199 -0
  59. package/dist/forum-sync/config.js +79 -0
  60. package/dist/forum-sync/discord-operations.js +154 -0
  61. package/dist/forum-sync/index.js +5 -0
  62. package/dist/forum-sync/markdown.js +117 -0
  63. package/dist/forum-sync/sync-to-discord.js +417 -0
  64. package/dist/forum-sync/sync-to-files.js +190 -0
  65. package/dist/forum-sync/types.js +53 -0
  66. package/dist/forum-sync/watchers.js +307 -0
  67. package/dist/gateway-consumer.js +232 -0
  68. package/dist/gateway-consumer.test.js +18 -0
  69. package/dist/genai-worker-wrapper.js +111 -0
  70. package/dist/genai-worker.js +311 -0
  71. package/dist/genai.js +232 -0
  72. package/dist/generated/browser.js +17 -0
  73. package/dist/generated/client.js +35 -0
  74. package/dist/generated/commonInputTypes.js +10 -0
  75. package/dist/generated/enums.js +30 -0
  76. package/dist/generated/internal/class.js +41 -0
  77. package/dist/generated/internal/prismaNamespace.js +239 -0
  78. package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
  79. package/dist/generated/models/bot_api_keys.js +1 -0
  80. package/dist/generated/models/bot_tokens.js +1 -0
  81. package/dist/generated/models/channel_agents.js +1 -0
  82. package/dist/generated/models/channel_directories.js +1 -0
  83. package/dist/generated/models/channel_mention_mode.js +1 -0
  84. package/dist/generated/models/channel_models.js +1 -0
  85. package/dist/generated/models/channel_verbosity.js +1 -0
  86. package/dist/generated/models/channel_worktrees.js +1 -0
  87. package/dist/generated/models/forum_sync_configs.js +1 -0
  88. package/dist/generated/models/global_models.js +1 -0
  89. package/dist/generated/models/ipc_requests.js +1 -0
  90. package/dist/generated/models/part_messages.js +1 -0
  91. package/dist/generated/models/scheduled_tasks.js +1 -0
  92. package/dist/generated/models/session_agents.js +1 -0
  93. package/dist/generated/models/session_models.js +1 -0
  94. package/dist/generated/models/session_start_sources.js +1 -0
  95. package/dist/generated/models/thread_sessions.js +1 -0
  96. package/dist/generated/models/thread_worktrees.js +1 -0
  97. package/dist/generated/models.js +1 -0
  98. package/dist/heap-monitor.js +95 -0
  99. package/dist/hrana-server.js +416 -0
  100. package/dist/hrana-server.test.js +368 -0
  101. package/dist/image-utils.js +112 -0
  102. package/dist/interaction-handler.js +327 -0
  103. package/dist/ipc-polling.js +251 -0
  104. package/dist/kimaki-digital-twin.e2e.test.js +165 -0
  105. package/dist/limit-heading-depth.js +25 -0
  106. package/dist/limit-heading-depth.test.js +105 -0
  107. package/dist/logger.js +160 -0
  108. package/dist/markdown.js +342 -0
  109. package/dist/markdown.test.js +253 -0
  110. package/dist/message-formatting.js +433 -0
  111. package/dist/message-formatting.test.js +73 -0
  112. package/dist/openai-realtime.js +228 -0
  113. package/dist/opencode-plugin-loading.e2e.test.js +91 -0
  114. package/dist/opencode-plugin.js +536 -0
  115. package/dist/opencode-plugin.test.js +98 -0
  116. package/dist/opencode.js +409 -0
  117. package/dist/privacy-sanitizer.js +105 -0
  118. package/dist/runtime-mode.js +51 -0
  119. package/dist/runtime-mode.test.js +115 -0
  120. package/dist/sentry.js +127 -0
  121. package/dist/session-handler/state.js +151 -0
  122. package/dist/session-handler.js +1874 -0
  123. package/dist/session-search.js +100 -0
  124. package/dist/session-search.test.js +40 -0
  125. package/dist/startup-service.js +153 -0
  126. package/dist/system-message.js +499 -0
  127. package/dist/task-runner.js +282 -0
  128. package/dist/task-schedule.js +191 -0
  129. package/dist/task-schedule.test.js +71 -0
  130. package/dist/thinking-utils.js +35 -0
  131. package/dist/thread-message-queue.e2e.test.js +781 -0
  132. package/dist/tools.js +359 -0
  133. package/dist/unnest-code-blocks.js +136 -0
  134. package/dist/unnest-code-blocks.test.js +641 -0
  135. package/dist/upgrade.js +114 -0
  136. package/dist/utils.js +109 -0
  137. package/dist/voice-handler.js +606 -0
  138. package/dist/voice.js +304 -0
  139. package/dist/voice.test.js +187 -0
  140. package/dist/wait-session.js +94 -0
  141. package/dist/worker-types.js +4 -0
  142. package/dist/worktree-utils.js +727 -0
  143. package/dist/xml.js +92 -0
  144. package/dist/xml.test.js +32 -0
  145. package/package.json +82 -0
  146. package/schema.prisma +246 -0
  147. package/skills/batch/SKILL.md +87 -0
  148. package/skills/critique/SKILL.md +129 -0
  149. package/skills/errore/SKILL.md +589 -0
  150. package/skills/goke/.prettierrc +5 -0
  151. package/skills/goke/CHANGELOG.md +40 -0
  152. package/skills/goke/LICENSE +21 -0
  153. package/skills/goke/README.md +666 -0
  154. package/skills/goke/SKILL.md +458 -0
  155. package/skills/goke/package.json +43 -0
  156. package/skills/goke/src/__test__/coerce.test.ts +411 -0
  157. package/skills/goke/src/__test__/index.test.ts +1798 -0
  158. package/skills/goke/src/__test__/types.test-d.ts +111 -0
  159. package/skills/goke/src/coerce.ts +547 -0
  160. package/skills/goke/src/goke.ts +1362 -0
  161. package/skills/goke/src/index.ts +16 -0
  162. package/skills/goke/src/mri.ts +164 -0
  163. package/skills/goke/tsconfig.json +15 -0
  164. package/skills/jitter/EDITOR.md +219 -0
  165. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  166. package/skills/jitter/SKILL.md +158 -0
  167. package/skills/jitter/jitter-clipboard.json +1042 -0
  168. package/skills/jitter/package.json +14 -0
  169. package/skills/jitter/tsconfig.json +15 -0
  170. package/skills/jitter/utils/actions.ts +212 -0
  171. package/skills/jitter/utils/export.ts +114 -0
  172. package/skills/jitter/utils/index.ts +141 -0
  173. package/skills/jitter/utils/snapshot.ts +154 -0
  174. package/skills/jitter/utils/traverse.ts +246 -0
  175. package/skills/jitter/utils/types.ts +279 -0
  176. package/skills/jitter/utils/wait.ts +133 -0
  177. package/skills/playwriter/SKILL.md +31 -0
  178. package/skills/security-review/SKILL.md +208 -0
  179. package/skills/simplify/SKILL.md +58 -0
  180. package/skills/termcast/SKILL.md +945 -0
  181. package/skills/tuistory/SKILL.md +250 -0
  182. package/skills/zustand-centralized-state/SKILL.md +582 -0
  183. package/src/__snapshots__/compact-session-context-no-system.md +35 -0
  184. package/src/__snapshots__/compact-session-context.md +41 -0
  185. package/src/__snapshots__/first-session-no-info.md +17 -0
  186. package/src/__snapshots__/first-session-with-info.md +23 -0
  187. package/src/__snapshots__/session-1.md +17 -0
  188. package/src/__snapshots__/session-2.md +5871 -0
  189. package/src/__snapshots__/session-3.md +17 -0
  190. package/src/__snapshots__/session-with-tools.md +5871 -0
  191. package/src/ai-tool-to-genai.test.ts +296 -0
  192. package/src/ai-tool-to-genai.ts +282 -0
  193. package/src/ai-tool.ts +39 -0
  194. package/src/bin.ts +108 -0
  195. package/src/bot-token.test.ts +171 -0
  196. package/src/bot-token.ts +159 -0
  197. package/src/channel-management.ts +172 -0
  198. package/src/cli-parsing.test.ts +132 -0
  199. package/src/cli.ts +3605 -0
  200. package/src/commands/abort.ts +112 -0
  201. package/src/commands/action-buttons.ts +376 -0
  202. package/src/commands/add-project.ts +152 -0
  203. package/src/commands/agent.ts +404 -0
  204. package/src/commands/ask-question.ts +330 -0
  205. package/src/commands/compact.ts +157 -0
  206. package/src/commands/context-usage.ts +199 -0
  207. package/src/commands/create-new-project.ts +179 -0
  208. package/src/commands/diff.ts +165 -0
  209. package/src/commands/file-upload.ts +389 -0
  210. package/src/commands/fork.ts +320 -0
  211. package/src/commands/gemini-apikey.ts +104 -0
  212. package/src/commands/login.ts +634 -0
  213. package/src/commands/mention-mode.ts +77 -0
  214. package/src/commands/merge-worktree.ts +177 -0
  215. package/src/commands/model.ts +961 -0
  216. package/src/commands/permissions.ts +261 -0
  217. package/src/commands/queue.ts +296 -0
  218. package/src/commands/remove-project.ts +155 -0
  219. package/src/commands/restart-opencode-server.ts +162 -0
  220. package/src/commands/resume.ts +242 -0
  221. package/src/commands/run-command.ts +123 -0
  222. package/src/commands/session-id.ts +109 -0
  223. package/src/commands/session.ts +250 -0
  224. package/src/commands/share.ts +106 -0
  225. package/src/commands/types.ts +25 -0
  226. package/src/commands/undo-redo.ts +221 -0
  227. package/src/commands/unset-model.ts +189 -0
  228. package/src/commands/upgrade.ts +52 -0
  229. package/src/commands/user-command.ts +193 -0
  230. package/src/commands/verbosity.ts +88 -0
  231. package/src/commands/worktree-settings.ts +79 -0
  232. package/src/commands/worktree.ts +431 -0
  233. package/src/condense-memory.ts +36 -0
  234. package/src/config.ts +148 -0
  235. package/src/database.ts +1530 -0
  236. package/src/db.test.ts +60 -0
  237. package/src/db.ts +190 -0
  238. package/src/discord-api.ts +35 -0
  239. package/src/discord-bot.ts +1316 -0
  240. package/src/discord-utils.test.ts +132 -0
  241. package/src/discord-utils.ts +767 -0
  242. package/src/errors.ts +213 -0
  243. package/src/escape-backticks.test.ts +469 -0
  244. package/src/format-tables.test.ts +223 -0
  245. package/src/format-tables.ts +145 -0
  246. package/src/forum-sync/config.ts +92 -0
  247. package/src/forum-sync/discord-operations.ts +241 -0
  248. package/src/forum-sync/index.ts +9 -0
  249. package/src/forum-sync/markdown.ts +176 -0
  250. package/src/forum-sync/sync-to-discord.ts +595 -0
  251. package/src/forum-sync/sync-to-files.ts +294 -0
  252. package/src/forum-sync/types.ts +175 -0
  253. package/src/forum-sync/watchers.ts +454 -0
  254. package/src/genai-worker-wrapper.ts +164 -0
  255. package/src/genai-worker.ts +386 -0
  256. package/src/genai.ts +321 -0
  257. package/src/generated/browser.ts +109 -0
  258. package/src/generated/client.ts +131 -0
  259. package/src/generated/commonInputTypes.ts +512 -0
  260. package/src/generated/enums.ts +46 -0
  261. package/src/generated/internal/class.ts +362 -0
  262. package/src/generated/internal/prismaNamespace.ts +2251 -0
  263. package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
  264. package/src/generated/models/bot_api_keys.ts +1288 -0
  265. package/src/generated/models/bot_tokens.ts +1577 -0
  266. package/src/generated/models/channel_agents.ts +1256 -0
  267. package/src/generated/models/channel_directories.ts +2104 -0
  268. package/src/generated/models/channel_mention_mode.ts +1300 -0
  269. package/src/generated/models/channel_models.ts +1288 -0
  270. package/src/generated/models/channel_verbosity.ts +1224 -0
  271. package/src/generated/models/channel_worktrees.ts +1308 -0
  272. package/src/generated/models/forum_sync_configs.ts +1452 -0
  273. package/src/generated/models/global_models.ts +1288 -0
  274. package/src/generated/models/ipc_requests.ts +1485 -0
  275. package/src/generated/models/part_messages.ts +1302 -0
  276. package/src/generated/models/scheduled_tasks.ts +2320 -0
  277. package/src/generated/models/session_agents.ts +1086 -0
  278. package/src/generated/models/session_models.ts +1114 -0
  279. package/src/generated/models/session_start_sources.ts +1408 -0
  280. package/src/generated/models/thread_sessions.ts +1599 -0
  281. package/src/generated/models/thread_worktrees.ts +1352 -0
  282. package/src/generated/models.ts +29 -0
  283. package/src/heap-monitor.ts +121 -0
  284. package/src/hrana-server.test.ts +428 -0
  285. package/src/hrana-server.ts +547 -0
  286. package/src/image-utils.ts +149 -0
  287. package/src/interaction-handler.ts +461 -0
  288. package/src/ipc-polling.ts +325 -0
  289. package/src/kimaki-digital-twin.e2e.test.ts +201 -0
  290. package/src/limit-heading-depth.test.ts +116 -0
  291. package/src/limit-heading-depth.ts +26 -0
  292. package/src/logger.ts +203 -0
  293. package/src/markdown.test.ts +360 -0
  294. package/src/markdown.ts +410 -0
  295. package/src/message-formatting.test.ts +81 -0
  296. package/src/message-formatting.ts +549 -0
  297. package/src/openai-realtime.ts +362 -0
  298. package/src/opencode-plugin-loading.e2e.test.ts +112 -0
  299. package/src/opencode-plugin.test.ts +108 -0
  300. package/src/opencode-plugin.ts +652 -0
  301. package/src/opencode.ts +554 -0
  302. package/src/privacy-sanitizer.ts +142 -0
  303. package/src/schema.sql +158 -0
  304. package/src/sentry.ts +137 -0
  305. package/src/session-handler/state.ts +232 -0
  306. package/src/session-handler.ts +2668 -0
  307. package/src/session-search.test.ts +50 -0
  308. package/src/session-search.ts +148 -0
  309. package/src/startup-service.ts +200 -0
  310. package/src/system-message.ts +568 -0
  311. package/src/task-runner.ts +425 -0
  312. package/src/task-schedule.test.ts +84 -0
  313. package/src/task-schedule.ts +287 -0
  314. package/src/thinking-utils.ts +61 -0
  315. package/src/thread-message-queue.e2e.test.ts +997 -0
  316. package/src/tools.ts +432 -0
  317. package/src/unnest-code-blocks.test.ts +679 -0
  318. package/src/unnest-code-blocks.ts +168 -0
  319. package/src/upgrade.ts +127 -0
  320. package/src/utils.ts +145 -0
  321. package/src/voice-handler.ts +852 -0
  322. package/src/voice.test.ts +219 -0
  323. package/src/voice.ts +444 -0
  324. package/src/wait-session.ts +147 -0
  325. package/src/worker-types.ts +64 -0
  326. package/src/worktree-utils.ts +988 -0
  327. package/src/xml.test.ts +38 -0
  328. package/src/xml.ts +121 -0
@@ -0,0 +1,100 @@
1
+ // Session search helpers for kimaki CLI commands.
2
+ // Parses string/regex queries and builds readable snippets from matched content.
3
+ export function parseSessionSearchPattern(query) {
4
+ const trimmedQuery = query.trim();
5
+ if (!trimmedQuery) {
6
+ return new Error('Search query cannot be empty');
7
+ }
8
+ const regexMatch = trimmedQuery.match(/^\/([\s\S]+)\/([a-z]*)$/);
9
+ if (!regexMatch) {
10
+ return {
11
+ mode: 'literal',
12
+ raw: trimmedQuery,
13
+ normalizedNeedle: trimmedQuery.toLowerCase(),
14
+ };
15
+ }
16
+ const pattern = regexMatch[1] || '';
17
+ const flags = regexMatch[2] || '';
18
+ try {
19
+ return {
20
+ mode: 'regex',
21
+ raw: trimmedQuery,
22
+ regex: new RegExp(pattern, flags),
23
+ };
24
+ }
25
+ catch (error) {
26
+ return new Error(`Invalid regex query "${trimmedQuery}": ${error instanceof Error ? error.message : String(error)}`);
27
+ }
28
+ }
29
+ export function findFirstSessionSearchHit({ text, searchPattern, }) {
30
+ if (searchPattern.mode === 'literal') {
31
+ const index = text.toLowerCase().indexOf(searchPattern.normalizedNeedle);
32
+ if (index < 0) {
33
+ return undefined;
34
+ }
35
+ return {
36
+ index,
37
+ length: searchPattern.raw.length,
38
+ };
39
+ }
40
+ searchPattern.regex.lastIndex = 0;
41
+ const match = searchPattern.regex.exec(text);
42
+ if (!match || match.index < 0) {
43
+ return undefined;
44
+ }
45
+ return {
46
+ index: match.index,
47
+ length: Math.max(match[0]?.length || 0, 1),
48
+ };
49
+ }
50
+ export function buildSessionSearchSnippet({ text, hit, contextLength = 90, }) {
51
+ const start = Math.max(0, hit.index - contextLength);
52
+ const end = Math.min(text.length, hit.index + hit.length + contextLength);
53
+ const prefix = start > 0 ? '...' : '';
54
+ const suffix = end < text.length ? '...' : '';
55
+ const body = text
56
+ .slice(start, end)
57
+ .replace(/[\r\n\t]+/g, ' ')
58
+ .replace(/\s+/g, ' ')
59
+ .trim();
60
+ return `${prefix}${body}${suffix}`;
61
+ }
62
+ function stringifyUnknown(value) {
63
+ if (value === undefined || value === null) {
64
+ return '';
65
+ }
66
+ if (typeof value === 'string') {
67
+ return value;
68
+ }
69
+ try {
70
+ return JSON.stringify(value);
71
+ }
72
+ catch {
73
+ return String(value);
74
+ }
75
+ }
76
+ export function getPartSearchTexts(part) {
77
+ switch (part.type) {
78
+ case 'text':
79
+ return part.text ? [part.text] : [];
80
+ case 'reasoning':
81
+ return part.text ? [part.text] : [];
82
+ case 'tool': {
83
+ const inputText = stringifyUnknown(part.state.input);
84
+ const outputText = part.state.status === 'completed'
85
+ ? stringifyUnknown(part.state.output)
86
+ : part.state.status === 'error'
87
+ ? part.state.error || ''
88
+ : '';
89
+ return [`tool:${part.tool}`, inputText, outputText].filter((entry) => {
90
+ return entry.trim().length > 0;
91
+ });
92
+ }
93
+ case 'file':
94
+ return [part.filename || '', part.url || ''].filter((entry) => {
95
+ return entry.trim().length > 0;
96
+ });
97
+ default:
98
+ return [];
99
+ }
100
+ }
@@ -0,0 +1,40 @@
1
+ // Tests for session search query parsing and snippet matching helpers.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { buildSessionSearchSnippet, findFirstSessionSearchHit, parseSessionSearchPattern, } from './session-search.js';
4
+ describe('session search helpers', () => {
5
+ test('returns error for invalid regex query', () => {
6
+ const parsed = parseSessionSearchPattern('/(unclosed/');
7
+ expect(parsed).toBeInstanceOf(Error);
8
+ });
9
+ test('returns snippets that include the matched substring', () => {
10
+ const cases = [
11
+ {
12
+ query: 'panic',
13
+ text: 'There was a PANIC in production',
14
+ expectedSubstring: 'PANIC',
15
+ },
16
+ {
17
+ query: '/error\\s+42/i',
18
+ text: 'Request failed with ERROR 42 in worker',
19
+ expectedSubstring: 'ERROR 42',
20
+ },
21
+ ];
22
+ cases.forEach(({ query, text, expectedSubstring }) => {
23
+ const parsed = parseSessionSearchPattern(query);
24
+ if (parsed instanceof Error) {
25
+ throw parsed;
26
+ }
27
+ const hit = findFirstSessionSearchHit({ text, searchPattern: parsed });
28
+ expect(hit).toBeDefined();
29
+ if (!hit) {
30
+ return;
31
+ }
32
+ const snippet = buildSessionSearchSnippet({
33
+ text,
34
+ hit,
35
+ contextLength: 8,
36
+ });
37
+ expect(snippet.toUpperCase()).toContain(expectedSubstring.toUpperCase());
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,153 @@
1
+ // Cross-platform startup service registration for kimaki daemon.
2
+ // Vendored from startup-run (MIT, github.com/vilicvane/startup-run) with
3
+ // significant simplifications: no abstract classes, no fs-extra, no winreg
4
+ // npm dep, no separate daemon process (kimaki's bin.ts already handles
5
+ // respawn/crash-loop). Just writes/deletes the platform service file.
6
+ //
7
+ // macOS: ~/Library/LaunchAgents/xyz.kimaki.plist (launchd)
8
+ // Linux: ~/.config/autostart/kimaki.desktop (XDG autostart)
9
+ // Windows: HKCU\Software\Microsoft\Windows\CurrentVersion\Run (registry)
10
+ import fs from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { execAsync } from './worktree-utils.js';
14
+ const SERVICE_NAME = 'xyz.kimaki';
15
+ function getServiceFilePath() {
16
+ switch (process.platform) {
17
+ case 'darwin':
18
+ return path.join(os.homedir(), 'Library', 'LaunchAgents', `${SERVICE_NAME}.plist`);
19
+ case 'linux':
20
+ return path.join(os.homedir(), '.config', 'autostart', 'kimaki.desktop');
21
+ case 'win32':
22
+ // No file — registry key, return a descriptive string for status display
23
+ return 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\kimaki';
24
+ default:
25
+ throw new Error(`Unsupported platform: ${process.platform}`);
26
+ }
27
+ }
28
+ function escapeXml(value) {
29
+ return value
30
+ .replace(/&/g, '&amp;')
31
+ .replace(/</g, '&lt;')
32
+ .replace(/>/g, '&gt;');
33
+ }
34
+ // Shell-escape a string for use in a Linux .desktop Exec= line.
35
+ // Wraps in double quotes if it contains spaces or special chars.
36
+ function shellEscape(value) {
37
+ if (/^[a-zA-Z0-9._/=-]+$/.test(value)) {
38
+ return value;
39
+ }
40
+ return `"${value.replace(/"/g, '\\"')}"`;
41
+ }
42
+ function buildMacOSPlist({ command, args, }) {
43
+ const segments = [command, ...args];
44
+ return `<?xml version="1.0" encoding="UTF-8"?>
45
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
46
+ <plist version="1.0">
47
+ <dict>
48
+ <key>Label</key>
49
+ <string>${SERVICE_NAME}</string>
50
+ <key>ProgramArguments</key>
51
+ <array>
52
+ ${segments.map((s) => ` <string>${escapeXml(s)}</string>`).join('\n')}
53
+ </array>
54
+ <key>RunAtLoad</key>
55
+ <true/>
56
+ <key>KeepAlive</key>
57
+ <false/>
58
+ </dict>
59
+ </plist>
60
+ `;
61
+ }
62
+ function buildLinuxDesktop({ command, args, }) {
63
+ const execLine = [command, ...args].map(shellEscape).join(' ');
64
+ return `[Desktop Entry]
65
+ Type=Application
66
+ Version=1.0
67
+ Name=Kimaki
68
+ Comment=Kimaki Discord Bot Daemon
69
+ Exec=${execLine}
70
+ StartupNotify=false
71
+ Terminal=false
72
+ `;
73
+ }
74
+ /**
75
+ * Register kimaki to start on user login.
76
+ * Writes the appropriate service file for the current platform.
77
+ */
78
+ export async function enableStartupService({ command, args, }) {
79
+ const platform = process.platform;
80
+ if (platform === 'darwin') {
81
+ const filePath = getServiceFilePath();
82
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
83
+ fs.writeFileSync(filePath, buildMacOSPlist({ command, args }));
84
+ }
85
+ else if (platform === 'linux') {
86
+ const filePath = getServiceFilePath();
87
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
88
+ fs.writeFileSync(filePath, buildLinuxDesktop({ command, args }));
89
+ }
90
+ else if (platform === 'win32') {
91
+ const execLine = [command, ...args]
92
+ .map((s) => {
93
+ return s.includes(' ') ? `"${s}"` : s;
94
+ })
95
+ .join(' ');
96
+ await execAsync(`reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v kimaki /t REG_SZ /d "${execLine}" /f`);
97
+ }
98
+ else {
99
+ throw new Error(`Unsupported platform: ${platform}`);
100
+ }
101
+ }
102
+ /**
103
+ * Unregister kimaki from user login startup.
104
+ */
105
+ export async function disableStartupService() {
106
+ const platform = process.platform;
107
+ if (platform === 'darwin' || platform === 'linux') {
108
+ const filePath = getServiceFilePath();
109
+ if (fs.existsSync(filePath)) {
110
+ fs.unlinkSync(filePath);
111
+ }
112
+ }
113
+ else if (platform === 'win32') {
114
+ await execAsync(`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v kimaki /f`).catch(() => {
115
+ // Key may not exist, ignore
116
+ });
117
+ }
118
+ else {
119
+ throw new Error(`Unsupported platform: ${platform}`);
120
+ }
121
+ }
122
+ /**
123
+ * Check if kimaki is registered as a startup service.
124
+ */
125
+ export async function isStartupServiceEnabled() {
126
+ const platform = process.platform;
127
+ if (platform === 'darwin' || platform === 'linux') {
128
+ return fs.existsSync(getServiceFilePath());
129
+ }
130
+ if (platform === 'win32') {
131
+ const result = await execAsync(`reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v kimaki`).catch(() => {
132
+ return null;
133
+ });
134
+ return result !== null;
135
+ }
136
+ return false;
137
+ }
138
+ /**
139
+ * Get a human-readable description of the service location for status display.
140
+ */
141
+ export function getServiceLocationDescription() {
142
+ const platform = process.platform;
143
+ if (platform === 'darwin') {
144
+ return `launchd: ${getServiceFilePath()}`;
145
+ }
146
+ if (platform === 'linux') {
147
+ return `XDG autostart: ${getServiceFilePath()}`;
148
+ }
149
+ if (platform === 'win32') {
150
+ return `registry: ${getServiceFilePath()}`;
151
+ }
152
+ return `unsupported platform: ${platform}`;
153
+ }