@otto-assistant/otto 0.1.2 → 0.7.16

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 (638) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-account-identity.js +62 -0
  7. package/dist/anthropic-account-identity.test.js +38 -0
  8. package/dist/anthropic-auth-plugin.js +917 -0
  9. package/dist/anthropic-auth-state.js +303 -0
  10. package/dist/anthropic-auth-state.test.js +150 -0
  11. package/dist/bin.js +152 -0
  12. package/dist/btw-prefix-detection.js +17 -0
  13. package/dist/btw-prefix-detection.test.js +63 -0
  14. package/dist/channel-management.js +259 -0
  15. package/dist/cli-parsing.test.js +142 -0
  16. package/dist/cli-send-thread.e2e.test.js +353 -0
  17. package/dist/cli-telegram-options.test.js +99 -0
  18. package/dist/cli.js +4210 -568
  19. package/dist/commands/abort.js +65 -0
  20. package/dist/commands/action-buttons.js +245 -0
  21. package/dist/commands/add-dir.js +124 -0
  22. package/dist/commands/add-dir.test.js +126 -0
  23. package/dist/commands/add-project.js +113 -0
  24. package/dist/commands/agent.js +355 -0
  25. package/dist/commands/ask-question.js +320 -0
  26. package/dist/commands/ask-question.test.js +92 -0
  27. package/dist/commands/btw.js +121 -0
  28. package/dist/commands/cli-commands-group-a.test.js +728 -0
  29. package/dist/commands/cli-commands-group-b.test.js +695 -0
  30. package/dist/commands/compact.js +120 -0
  31. package/dist/commands/context-usage.js +140 -0
  32. package/dist/commands/create-new-project.js +130 -0
  33. package/dist/commands/diff.js +63 -0
  34. package/dist/commands/discord-commands-group-a.test.js +655 -0
  35. package/dist/commands/discord-commands-group-b.test.js +595 -0
  36. package/dist/commands/discord-commands-group-c.test.js +739 -0
  37. package/dist/commands/file-upload.js +275 -0
  38. package/dist/commands/fork-subagent.js +177 -0
  39. package/dist/commands/fork.js +262 -0
  40. package/dist/commands/gemini-apikey.js +70 -0
  41. package/dist/commands/login.js +893 -0
  42. package/dist/commands/mcp.js +239 -0
  43. package/dist/commands/memory-snapshot.js +24 -0
  44. package/dist/commands/mention-mode.js +44 -0
  45. package/dist/commands/merge-worktree.js +162 -0
  46. package/dist/commands/model-variant.js +369 -0
  47. package/dist/commands/model.js +798 -0
  48. package/dist/commands/new-worktree.js +465 -0
  49. package/dist/commands/paginated-select.js +57 -0
  50. package/dist/commands/permissions.js +274 -0
  51. package/dist/commands/queue.js +223 -0
  52. package/dist/commands/remove-project.js +115 -0
  53. package/dist/commands/restart-opencode-server.js +127 -0
  54. package/dist/commands/resume.js +149 -0
  55. package/dist/commands/run-command.js +79 -0
  56. package/dist/commands/screenshare.js +303 -0
  57. package/dist/commands/screenshare.test.js +20 -0
  58. package/dist/commands/session-id.js +78 -0
  59. package/dist/commands/session.js +176 -0
  60. package/dist/commands/share.js +80 -0
  61. package/dist/commands/tasks.js +205 -0
  62. package/dist/commands/thread-deletion-sync.js +50 -0
  63. package/dist/commands/types.js +2 -0
  64. package/dist/commands/undo-redo.js +305 -0
  65. package/dist/commands/unset-model.js +139 -0
  66. package/dist/commands/upgrade.js +48 -0
  67. package/dist/commands/user-command.js +155 -0
  68. package/dist/commands/verbosity.js +125 -0
  69. package/dist/commands/vscode.js +269 -0
  70. package/dist/commands/worktree-settings.js +43 -0
  71. package/dist/commands/worktrees.js +468 -0
  72. package/dist/condense-memory.js +33 -0
  73. package/dist/config.js +100 -255
  74. package/dist/context-awareness-plugin.js +340 -0
  75. package/dist/context-awareness-plugin.test.js +126 -0
  76. package/dist/critique-utils.js +95 -0
  77. package/dist/database.js +1355 -0
  78. package/dist/db.js +260 -0
  79. package/dist/db.test.js +138 -0
  80. package/dist/debounce-timeout.js +28 -0
  81. package/dist/debounced-process-flush.js +77 -0
  82. package/dist/discord-bot.js +1124 -0
  83. package/dist/discord-command-registration.js +567 -0
  84. package/dist/discord-urls.js +82 -0
  85. package/dist/discord-utils.js +616 -0
  86. package/dist/discord-utils.test.js +134 -0
  87. package/dist/errors.js +179 -0
  88. package/dist/escape-backticks.test.js +429 -0
  89. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  90. package/dist/eventsource-parser.test.js +327 -0
  91. package/dist/exec-async.js +26 -0
  92. package/dist/external-opencode-sync.js +480 -0
  93. package/dist/format-tables.js +491 -0
  94. package/dist/format-tables.test.js +478 -0
  95. package/dist/forum-sync/config.js +79 -0
  96. package/dist/forum-sync/discord-operations.js +154 -0
  97. package/dist/forum-sync/index.js +5 -0
  98. package/dist/forum-sync/markdown.js +113 -0
  99. package/dist/forum-sync/sync-to-discord.js +417 -0
  100. package/dist/forum-sync/sync-to-files.js +190 -0
  101. package/dist/forum-sync/types.js +53 -0
  102. package/dist/forum-sync/watchers.js +307 -0
  103. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  104. package/dist/gateway-proxy.e2e.test.js +485 -0
  105. package/dist/genai-worker-wrapper.js +111 -0
  106. package/dist/genai-worker.js +311 -0
  107. package/dist/genai.js +232 -0
  108. package/dist/generated/browser.js +17 -0
  109. package/dist/generated/client.js +37 -0
  110. package/dist/generated/commonInputTypes.js +10 -0
  111. package/dist/generated/enums.js +58 -0
  112. package/dist/generated/internal/class.js +49 -0
  113. package/dist/generated/internal/prismaNamespace.js +254 -0
  114. package/dist/generated/internal/prismaNamespaceBrowser.js +224 -0
  115. package/dist/generated/models/bot_api_keys.js +1 -0
  116. package/dist/generated/models/bot_tokens.js +1 -0
  117. package/dist/generated/models/channel_agents.js +1 -0
  118. package/dist/generated/models/channel_directories.js +1 -0
  119. package/dist/generated/models/channel_mention_mode.js +1 -0
  120. package/dist/generated/models/channel_models.js +1 -0
  121. package/dist/generated/models/channel_verbosity.js +1 -0
  122. package/dist/generated/models/channel_worktrees.js +1 -0
  123. package/dist/generated/models/forum_sync_configs.js +1 -0
  124. package/dist/generated/models/global_models.js +1 -0
  125. package/dist/generated/models/ipc_requests.js +1 -0
  126. package/dist/generated/models/part_messages.js +1 -0
  127. package/dist/generated/models/scheduled_tasks.js +1 -0
  128. package/dist/generated/models/session_agents.js +1 -0
  129. package/dist/generated/models/session_events.js +1 -0
  130. package/dist/generated/models/session_models.js +1 -0
  131. package/dist/generated/models/session_start_sources.js +1 -0
  132. package/dist/generated/models/thread_sessions.js +1 -0
  133. package/dist/generated/models/thread_worktrees.js +1 -0
  134. package/dist/generated/models.js +1 -0
  135. package/dist/heap-monitor.js +122 -0
  136. package/dist/hrana-server.js +251 -0
  137. package/dist/hrana-server.test.js +370 -0
  138. package/dist/html-actions.js +123 -0
  139. package/dist/html-actions.test.js +70 -0
  140. package/dist/html-components.js +117 -0
  141. package/dist/html-components.test.js +34 -0
  142. package/dist/image-optimizer-plugin.js +153 -0
  143. package/dist/image-utils.js +112 -0
  144. package/dist/interaction-handler.js +420 -0
  145. package/dist/ipc-polling.js +327 -0
  146. package/dist/ipc-tools-plugin.js +193 -0
  147. package/dist/ipc-utils.js +18 -0
  148. package/dist/limit-heading-depth.js +25 -0
  149. package/dist/limit-heading-depth.test.js +105 -0
  150. package/dist/logger.js +171 -0
  151. package/dist/markdown.js +342 -0
  152. package/dist/markdown.test.js +264 -0
  153. package/dist/memory-overview-plugin.js +128 -0
  154. package/dist/message-finish-field.e2e.test.js +168 -0
  155. package/dist/message-formatting.js +415 -0
  156. package/dist/message-formatting.test.js +115 -0
  157. package/dist/message-preprocessing.js +359 -0
  158. package/dist/onboarding-tutorial.js +163 -0
  159. package/dist/onboarding-welcome.js +37 -0
  160. package/dist/openai-realtime.js +224 -0
  161. package/dist/opencode-command-detection.js +65 -0
  162. package/dist/opencode-command-detection.test.js +240 -0
  163. package/dist/opencode-command.js +131 -0
  164. package/dist/opencode-command.test.js +48 -0
  165. package/dist/opencode-interrupt-plugin.js +388 -0
  166. package/dist/opencode-interrupt-plugin.test.js +463 -0
  167. package/dist/opencode.js +1124 -0
  168. package/dist/otto/branding.js +22 -0
  169. package/dist/otto/index.js +21 -0
  170. package/dist/otto-digital-twin.e2e.test.js +161 -0
  171. package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
  172. package/dist/otto-opencode-plugin.js +21 -0
  173. package/dist/otto-opencode-plugin.test.js +98 -0
  174. package/dist/parse-permission-rules.test.js +117 -0
  175. package/dist/patch-text-parser.js +97 -0
  176. package/dist/plugin-logger.js +68 -0
  177. package/dist/privacy-sanitizer.js +105 -0
  178. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  179. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  180. package/dist/queue-advanced-e2e-setup.js +790 -0
  181. package/dist/queue-advanced-footer.e2e.test.js +481 -0
  182. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  183. package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -0
  184. package/dist/queue-advanced-question.e2e.test.js +261 -0
  185. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  186. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  187. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  188. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  189. package/dist/queue-question-select-drain.e2e.test.js +256 -0
  190. package/dist/runtime-idle-sweeper.js +52 -0
  191. package/dist/runtime-lifecycle.e2e.test.js +514 -0
  192. package/dist/sentry.js +23 -0
  193. package/dist/session-handler/agent-utils.js +67 -0
  194. package/dist/session-handler/event-stream-state.js +475 -0
  195. package/dist/session-handler/event-stream-state.test.js +632 -0
  196. package/dist/session-handler/model-utils.js +147 -0
  197. package/dist/session-handler/opencode-session-event-log.js +94 -0
  198. package/dist/session-handler/thread-runtime-state.js +131 -0
  199. package/dist/session-handler/thread-session-runtime.js +3390 -0
  200. package/dist/session-handler.js +9 -0
  201. package/dist/session-search.js +100 -0
  202. package/dist/session-search.test.js +40 -0
  203. package/dist/session-title-rename.test.js +92 -0
  204. package/dist/skill-filter.js +31 -0
  205. package/dist/skill-filter.test.js +65 -0
  206. package/dist/startup-service.js +153 -0
  207. package/dist/startup-time.e2e.test.js +296 -0
  208. package/dist/store.js +19 -0
  209. package/dist/subagent-rate-limit-plugin.js +175 -0
  210. package/dist/system-message.js +702 -0
  211. package/dist/system-message.test.js +697 -0
  212. package/dist/task-runner.js +530 -0
  213. package/dist/task-schedule.js +213 -0
  214. package/dist/task-schedule.test.js +71 -0
  215. package/dist/test-utils.js +313 -0
  216. package/dist/thinking-utils.js +35 -0
  217. package/dist/thread-message-queue.e2e.test.js +1111 -0
  218. package/dist/tools.js +357 -0
  219. package/dist/undo-redo.e2e.test.js +161 -0
  220. package/dist/unnest-code-blocks.js +146 -0
  221. package/dist/unnest-code-blocks.test.js +673 -0
  222. package/dist/upgrade.js +156 -0
  223. package/dist/utils.js +172 -0
  224. package/dist/utils.test.js +130 -0
  225. package/dist/voice-attachment.js +34 -0
  226. package/dist/voice-handler.js +646 -0
  227. package/dist/voice-message.e2e.test.js +1021 -0
  228. package/dist/voice.js +456 -0
  229. package/dist/voice.test.js +235 -0
  230. package/dist/wait-session.js +171 -0
  231. package/dist/websockify.js +69 -0
  232. package/dist/worker-types.js +4 -0
  233. package/dist/worktree-lifecycle.e2e.test.js +311 -0
  234. package/dist/worktree-utils.js +3 -0
  235. package/dist/worktrees.js +991 -0
  236. package/dist/worktrees.test.js +415 -0
  237. package/dist/xml.js +92 -0
  238. package/dist/xml.test.js +32 -0
  239. package/package.json +90 -38
  240. package/schema.prisma +303 -0
  241. package/skills/batch/SKILL.md +87 -0
  242. package/skills/critique/SKILL.md +112 -0
  243. package/skills/egaki/SKILL.md +100 -0
  244. package/skills/errore/SKILL.md +647 -0
  245. package/skills/event-sourcing-state/SKILL.md +252 -0
  246. package/skills/goke/SKILL.md +38 -0
  247. package/skills/jitter/EDITOR.md +219 -0
  248. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  249. package/skills/jitter/SKILL.md +158 -0
  250. package/skills/jitter/jitter-clipboard.json +1042 -0
  251. package/skills/jitter/package.json +14 -0
  252. package/skills/jitter/tsconfig.json +15 -0
  253. package/skills/jitter/utils/actions.ts +212 -0
  254. package/skills/jitter/utils/export.ts +114 -0
  255. package/skills/jitter/utils/index.ts +141 -0
  256. package/skills/jitter/utils/snapshot.ts +154 -0
  257. package/skills/jitter/utils/traverse.ts +246 -0
  258. package/skills/jitter/utils/types.ts +279 -0
  259. package/skills/jitter/utils/wait.ts +133 -0
  260. package/skills/lintcn/SKILL.md +873 -0
  261. package/skills/manual-kimaki-upstream-adapt/SKILL.md +114 -0
  262. package/skills/new-skill/SKILL.md +237 -0
  263. package/skills/npm-package/SKILL.md +617 -0
  264. package/skills/opensrc/SKILL.md +78 -0
  265. package/skills/otto-publish/SKILL.md +61 -0
  266. package/skills/playwriter/SKILL.md +35 -0
  267. package/skills/profano/SKILL.md +16 -0
  268. package/skills/proxyman/SKILL.md +215 -0
  269. package/skills/security-review/SKILL.md +208 -0
  270. package/skills/sigillo/SKILL.md +101 -0
  271. package/skills/simplify/SKILL.md +58 -0
  272. package/skills/spiceflow/SKILL.md +28 -0
  273. package/skills/termcast/SKILL.md +945 -0
  274. package/skills/tuistory/SKILL.md +98 -0
  275. package/skills/usecomputer/SKILL.md +264 -0
  276. package/skills/x-articles/SKILL.md +554 -0
  277. package/skills/zele/SKILL.md +49 -0
  278. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  279. package/src/agent-model.e2e.test.ts +979 -0
  280. package/src/ai-tool-to-genai.test.ts +296 -0
  281. package/src/ai-tool-to-genai.ts +283 -0
  282. package/src/ai-tool.ts +39 -0
  283. package/src/anthropic-account-identity.test.ts +52 -0
  284. package/src/anthropic-account-identity.ts +77 -0
  285. package/src/anthropic-auth-plugin.ts +1139 -0
  286. package/src/anthropic-auth-state.test.ts +187 -0
  287. package/src/anthropic-auth-state.ts +386 -0
  288. package/src/bin.ts +182 -0
  289. package/src/btw-prefix-detection.test.ts +73 -0
  290. package/src/btw-prefix-detection.ts +23 -0
  291. package/src/channel-management.ts +376 -0
  292. package/src/cli-parsing.test.ts +197 -0
  293. package/src/cli-send-thread.e2e.test.ts +463 -0
  294. package/src/cli-telegram-options.test.ts +114 -0
  295. package/src/cli.ts +5718 -580
  296. package/src/commands/abort.ts +89 -0
  297. package/src/commands/action-buttons.ts +364 -0
  298. package/src/commands/add-dir.test.ts +154 -0
  299. package/src/commands/add-dir.ts +175 -0
  300. package/src/commands/add-project.ts +149 -0
  301. package/src/commands/agent.ts +496 -0
  302. package/src/commands/ask-question.test.ts +111 -0
  303. package/src/commands/ask-question.ts +455 -0
  304. package/src/commands/btw.ts +184 -0
  305. package/src/commands/cli-commands-group-a.test.ts +837 -0
  306. package/src/commands/cli-commands-group-b.test.ts +800 -0
  307. package/src/commands/compact.ts +157 -0
  308. package/src/commands/context-usage.ts +199 -0
  309. package/src/commands/create-new-project.ts +190 -0
  310. package/src/commands/diff.ts +91 -0
  311. package/src/commands/discord-commands-group-a.test.ts +789 -0
  312. package/src/commands/discord-commands-group-b.test.ts +648 -0
  313. package/src/commands/discord-commands-group-c.test.ts +882 -0
  314. package/src/commands/file-upload.ts +389 -0
  315. package/src/commands/fork-subagent.ts +263 -0
  316. package/src/commands/fork.ts +386 -0
  317. package/src/commands/gemini-apikey.ts +104 -0
  318. package/src/commands/login.ts +1181 -0
  319. package/src/commands/mcp.ts +307 -0
  320. package/src/commands/memory-snapshot.ts +30 -0
  321. package/src/commands/mention-mode.ts +68 -0
  322. package/src/commands/merge-worktree.ts +226 -0
  323. package/src/commands/model-variant.ts +488 -0
  324. package/src/commands/model.ts +1082 -0
  325. package/src/commands/new-worktree.ts +645 -0
  326. package/src/commands/paginated-select.ts +81 -0
  327. package/src/commands/permissions.ts +397 -0
  328. package/src/commands/queue.ts +293 -0
  329. package/src/commands/remove-project.ts +155 -0
  330. package/src/commands/restart-opencode-server.ts +162 -0
  331. package/src/commands/resume.ts +230 -0
  332. package/src/commands/run-command.ts +123 -0
  333. package/src/commands/screenshare.test.ts +30 -0
  334. package/src/commands/screenshare.ts +366 -0
  335. package/src/commands/session-id.ts +109 -0
  336. package/src/commands/session.ts +227 -0
  337. package/src/commands/share.ts +106 -0
  338. package/src/commands/tasks.ts +293 -0
  339. package/src/commands/thread-deletion-sync.ts +80 -0
  340. package/src/commands/types.ts +25 -0
  341. package/src/commands/undo-redo.ts +386 -0
  342. package/src/commands/unset-model.ts +174 -0
  343. package/src/commands/upgrade.ts +59 -0
  344. package/src/commands/user-command.ts +198 -0
  345. package/src/commands/verbosity.ts +173 -0
  346. package/src/commands/vscode.ts +342 -0
  347. package/src/commands/worktree-settings.ts +70 -0
  348. package/src/commands/worktrees.ts +645 -0
  349. package/src/condense-memory.ts +36 -0
  350. package/src/config.ts +103 -339
  351. package/src/context-awareness-plugin.test.ts +144 -0
  352. package/src/context-awareness-plugin.ts +469 -0
  353. package/src/critique-utils.ts +139 -0
  354. package/src/database.ts +1949 -0
  355. package/src/db.test.ts +162 -0
  356. package/src/db.ts +295 -0
  357. package/src/debounce-timeout.ts +43 -0
  358. package/src/debounced-process-flush.ts +104 -0
  359. package/src/discord-bot.ts +1507 -0
  360. package/src/discord-command-registration.ts +752 -0
  361. package/src/discord-urls.ts +89 -0
  362. package/src/discord-utils.test.ts +153 -0
  363. package/src/discord-utils.ts +846 -0
  364. package/src/errors.ts +232 -0
  365. package/src/escape-backticks.test.ts +469 -0
  366. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  367. package/src/eventsource-parser.test.ts +351 -0
  368. package/src/exec-async.ts +35 -0
  369. package/src/external-opencode-sync.ts +685 -0
  370. package/src/format-tables.test.ts +515 -0
  371. package/src/format-tables.ts +718 -0
  372. package/src/forum-sync/config.ts +92 -0
  373. package/src/forum-sync/discord-operations.ts +241 -0
  374. package/src/forum-sync/index.ts +9 -0
  375. package/src/forum-sync/markdown.ts +172 -0
  376. package/src/forum-sync/sync-to-discord.ts +595 -0
  377. package/src/forum-sync/sync-to-files.ts +294 -0
  378. package/src/forum-sync/types.ts +175 -0
  379. package/src/forum-sync/watchers.ts +454 -0
  380. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  381. package/src/gateway-proxy.e2e.test.ts +644 -0
  382. package/src/genai-worker-wrapper.ts +164 -0
  383. package/src/genai-worker.ts +386 -0
  384. package/src/genai.ts +321 -0
  385. package/src/generated/browser.ts +114 -0
  386. package/src/generated/client.ts +138 -0
  387. package/src/generated/commonInputTypes.ts +770 -0
  388. package/src/generated/enums.ts +98 -0
  389. package/src/generated/internal/class.ts +384 -0
  390. package/src/generated/internal/prismaNamespace.ts +2394 -0
  391. package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
  392. package/src/generated/models/bot_api_keys.ts +1288 -0
  393. package/src/generated/models/bot_tokens.ts +1700 -0
  394. package/src/generated/models/channel_agents.ts +1256 -0
  395. package/src/generated/models/channel_directories.ts +1859 -0
  396. package/src/generated/models/channel_mention_mode.ts +1300 -0
  397. package/src/generated/models/channel_models.ts +1288 -0
  398. package/src/generated/models/channel_verbosity.ts +1228 -0
  399. package/src/generated/models/channel_worktrees.ts +1300 -0
  400. package/src/generated/models/forum_sync_configs.ts +1452 -0
  401. package/src/generated/models/global_models.ts +1288 -0
  402. package/src/generated/models/ipc_requests.ts +1485 -0
  403. package/src/generated/models/part_messages.ts +1302 -0
  404. package/src/generated/models/scheduled_tasks.ts +2320 -0
  405. package/src/generated/models/session_agents.ts +1086 -0
  406. package/src/generated/models/session_events.ts +1439 -0
  407. package/src/generated/models/session_models.ts +1114 -0
  408. package/src/generated/models/session_start_sources.ts +1408 -0
  409. package/src/generated/models/thread_sessions.ts +1781 -0
  410. package/src/generated/models/thread_worktrees.ts +1356 -0
  411. package/src/generated/models.ts +30 -0
  412. package/src/heap-monitor.ts +152 -0
  413. package/src/hrana-server.test.ts +434 -0
  414. package/src/hrana-server.ts +299 -0
  415. package/src/html-actions.test.ts +87 -0
  416. package/src/html-actions.ts +174 -0
  417. package/src/html-components.test.ts +38 -0
  418. package/src/html-components.ts +181 -0
  419. package/src/image-optimizer-plugin.ts +194 -0
  420. package/src/image-utils.ts +149 -0
  421. package/src/interaction-handler.ts +610 -0
  422. package/src/ipc-polling.ts +427 -0
  423. package/src/ipc-tools-plugin.ts +236 -0
  424. package/src/ipc-utils.ts +29 -0
  425. package/src/limit-heading-depth.test.ts +116 -0
  426. package/src/limit-heading-depth.ts +26 -0
  427. package/src/logger.ts +215 -0
  428. package/src/markdown.test.ts +315 -0
  429. package/src/markdown.ts +410 -0
  430. package/src/memory-overview-plugin.ts +163 -0
  431. package/src/message-finish-field.e2e.test.ts +195 -0
  432. package/src/message-formatting.test.ts +126 -0
  433. package/src/message-formatting.ts +535 -0
  434. package/src/message-preprocessing.ts +488 -0
  435. package/src/onboarding-tutorial.ts +167 -0
  436. package/src/onboarding-welcome.ts +49 -0
  437. package/src/openai-realtime.ts +358 -0
  438. package/src/opencode-command-detection.test.ts +307 -0
  439. package/src/opencode-command-detection.ts +76 -0
  440. package/src/opencode-command.test.ts +70 -0
  441. package/src/opencode-command.ts +191 -0
  442. package/src/opencode-interrupt-plugin.test.ts +682 -0
  443. package/src/opencode-interrupt-plugin.ts +507 -0
  444. package/src/opencode.ts +1462 -0
  445. package/src/otto/branding.ts +23 -0
  446. package/src/otto/index.ts +22 -0
  447. package/src/otto-digital-twin.e2e.test.ts +199 -0
  448. package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
  449. package/src/otto-opencode-plugin.test.ts +108 -0
  450. package/src/otto-opencode-plugin.ts +22 -0
  451. package/src/parse-permission-rules.test.ts +127 -0
  452. package/src/patch-text-parser.ts +107 -0
  453. package/src/plugin-logger.ts +84 -0
  454. package/src/privacy-sanitizer.ts +142 -0
  455. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  456. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  457. package/src/queue-advanced-e2e-setup.ts +877 -0
  458. package/src/queue-advanced-footer.e2e.test.ts +591 -0
  459. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  460. package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -0
  461. package/src/queue-advanced-question.e2e.test.ts +316 -0
  462. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  463. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  464. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  465. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  466. package/src/queue-question-select-drain.e2e.test.ts +327 -0
  467. package/src/runtime-idle-sweeper.ts +76 -0
  468. package/src/runtime-lifecycle.e2e.test.ts +651 -0
  469. package/src/schema.sql +174 -0
  470. package/src/sentry.ts +26 -0
  471. package/src/session-handler/agent-utils.ts +99 -0
  472. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  473. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  474. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  475. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  476. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  477. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  478. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  479. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  480. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  481. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  482. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  483. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  484. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  485. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  486. package/src/session-handler/event-stream-state.test.ts +717 -0
  487. package/src/session-handler/event-stream-state.ts +706 -0
  488. package/src/session-handler/model-utils.ts +217 -0
  489. package/src/session-handler/opencode-session-event-log.ts +130 -0
  490. package/src/session-handler/thread-runtime-state.ts +247 -0
  491. package/src/session-handler/thread-session-runtime.ts +4440 -0
  492. package/src/session-handler.ts +15 -0
  493. package/src/session-search.test.ts +50 -0
  494. package/src/session-search.ts +148 -0
  495. package/src/session-title-rename.test.ts +130 -0
  496. package/src/skill-filter.test.ts +83 -0
  497. package/src/skill-filter.ts +42 -0
  498. package/src/startup-service.ts +200 -0
  499. package/src/startup-time.e2e.test.ts +373 -0
  500. package/src/store.ts +139 -0
  501. package/src/subagent-rate-limit-plugin.ts +218 -0
  502. package/src/system-message.test.ts +710 -0
  503. package/src/system-message.ts +814 -0
  504. package/src/task-runner.ts +725 -0
  505. package/src/task-schedule.test.ts +84 -0
  506. package/src/task-schedule.ts +317 -0
  507. package/src/test-utils.ts +451 -0
  508. package/src/thinking-utils.ts +61 -0
  509. package/src/thread-message-queue.e2e.test.ts +1350 -0
  510. package/src/tools.ts +430 -0
  511. package/src/undici.d.ts +12 -0
  512. package/src/undo-redo.e2e.test.ts +209 -0
  513. package/src/unnest-code-blocks.test.ts +713 -0
  514. package/src/unnest-code-blocks.ts +185 -0
  515. package/src/upgrade.ts +185 -0
  516. package/src/utils.test.ts +155 -0
  517. package/src/utils.ts +265 -0
  518. package/src/voice-attachment.ts +51 -0
  519. package/src/voice-handler.ts +908 -0
  520. package/src/voice-message.e2e.test.ts +1255 -0
  521. package/src/voice.test.ts +281 -0
  522. package/src/voice.ts +638 -0
  523. package/src/wait-session.ts +273 -0
  524. package/src/websockify.ts +101 -0
  525. package/src/worker-types.ts +64 -0
  526. package/src/worktree-lifecycle.e2e.test.ts +396 -0
  527. package/src/worktree-utils.ts +4 -0
  528. package/src/worktrees.test.ts +489 -0
  529. package/src/worktrees.ts +1370 -0
  530. package/src/xml.test.ts +38 -0
  531. package/src/xml.ts +121 -0
  532. package/README.md +0 -142
  533. package/dist/cli.d.ts +0 -3
  534. package/dist/cli.d.ts.map +0 -1
  535. package/dist/cli.js.map +0 -1
  536. package/dist/config.d.ts +0 -39
  537. package/dist/config.d.ts.map +0 -1
  538. package/dist/config.js.map +0 -1
  539. package/dist/config.test.d.ts +0 -2
  540. package/dist/config.test.d.ts.map +0 -1
  541. package/dist/config.test.js +0 -202
  542. package/dist/config.test.js.map +0 -1
  543. package/dist/detect.d.ts +0 -9
  544. package/dist/detect.d.ts.map +0 -1
  545. package/dist/detect.js +0 -40
  546. package/dist/detect.js.map +0 -1
  547. package/dist/detect.test.d.ts +0 -2
  548. package/dist/detect.test.d.ts.map +0 -1
  549. package/dist/detect.test.js +0 -26
  550. package/dist/detect.test.js.map +0 -1
  551. package/dist/docker.d.ts +0 -7
  552. package/dist/docker.d.ts.map +0 -1
  553. package/dist/docker.js +0 -17
  554. package/dist/docker.js.map +0 -1
  555. package/dist/docker.test.d.ts +0 -2
  556. package/dist/docker.test.d.ts.map +0 -1
  557. package/dist/docker.test.js +0 -12
  558. package/dist/docker.test.js.map +0 -1
  559. package/dist/health.d.ts +0 -31
  560. package/dist/health.d.ts.map +0 -1
  561. package/dist/health.js +0 -117
  562. package/dist/health.js.map +0 -1
  563. package/dist/health.test.d.ts +0 -2
  564. package/dist/health.test.d.ts.map +0 -1
  565. package/dist/health.test.js +0 -52
  566. package/dist/health.test.js.map +0 -1
  567. package/dist/index.d.ts +0 -20
  568. package/dist/index.d.ts.map +0 -1
  569. package/dist/index.js +0 -15
  570. package/dist/index.js.map +0 -1
  571. package/dist/index.test.d.ts +0 -2
  572. package/dist/index.test.d.ts.map +0 -1
  573. package/dist/index.test.js +0 -8
  574. package/dist/index.test.js.map +0 -1
  575. package/dist/installer.d.ts +0 -10
  576. package/dist/installer.d.ts.map +0 -1
  577. package/dist/installer.js +0 -50
  578. package/dist/installer.js.map +0 -1
  579. package/dist/installer.test.d.ts +0 -2
  580. package/dist/installer.test.d.ts.map +0 -1
  581. package/dist/installer.test.js +0 -43
  582. package/dist/installer.test.js.map +0 -1
  583. package/dist/lifecycle.d.ts +0 -10
  584. package/dist/lifecycle.d.ts.map +0 -1
  585. package/dist/lifecycle.js +0 -45
  586. package/dist/lifecycle.js.map +0 -1
  587. package/dist/lifecycle.test.d.ts +0 -2
  588. package/dist/lifecycle.test.d.ts.map +0 -1
  589. package/dist/lifecycle.test.js +0 -20
  590. package/dist/lifecycle.test.js.map +0 -1
  591. package/dist/manifest.d.ts +0 -18
  592. package/dist/manifest.d.ts.map +0 -1
  593. package/dist/manifest.js +0 -30
  594. package/dist/manifest.js.map +0 -1
  595. package/dist/skills-baseline.d.ts +0 -7
  596. package/dist/skills-baseline.d.ts.map +0 -1
  597. package/dist/skills-baseline.js +0 -9
  598. package/dist/skills-baseline.js.map +0 -1
  599. package/dist/skills.d.ts +0 -110
  600. package/dist/skills.d.ts.map +0 -1
  601. package/dist/skills.js +0 -429
  602. package/dist/skills.js.map +0 -1
  603. package/dist/skills.test.d.ts +0 -2
  604. package/dist/skills.test.d.ts.map +0 -1
  605. package/dist/skills.test.js +0 -416
  606. package/dist/skills.test.js.map +0 -1
  607. package/dist/sync.d.ts +0 -10
  608. package/dist/sync.d.ts.map +0 -1
  609. package/dist/sync.js +0 -39
  610. package/dist/sync.js.map +0 -1
  611. package/dist/tenant.d.ts +0 -13
  612. package/dist/tenant.d.ts.map +0 -1
  613. package/dist/tenant.js +0 -105
  614. package/dist/tenant.js.map +0 -1
  615. package/dist/tenant.test.d.ts +0 -2
  616. package/dist/tenant.test.d.ts.map +0 -1
  617. package/dist/tenant.test.js +0 -37
  618. package/dist/tenant.test.js.map +0 -1
  619. package/src/config.test.ts +0 -237
  620. package/src/detect.test.ts +0 -29
  621. package/src/detect.ts +0 -52
  622. package/src/docker.test.ts +0 -12
  623. package/src/docker.ts +0 -23
  624. package/src/health.test.ts +0 -61
  625. package/src/health.ts +0 -158
  626. package/src/index.test.ts +0 -8
  627. package/src/index.ts +0 -62
  628. package/src/installer.test.ts +0 -52
  629. package/src/installer.ts +0 -62
  630. package/src/lifecycle.test.ts +0 -23
  631. package/src/lifecycle.ts +0 -49
  632. package/src/manifest.ts +0 -42
  633. package/src/skills-baseline.ts +0 -14
  634. package/src/skills.test.ts +0 -503
  635. package/src/skills.ts +0 -512
  636. package/src/sync.ts +0 -53
  637. package/src/tenant.test.ts +0 -49
  638. package/src/tenant.ts +0 -120
package/src/skills.ts DELETED
@@ -1,512 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import os from "node:os"
4
- import { execFileSync } from "node:child_process"
5
- import { OPENCODE_CONFIG_DIR } from "./manifest.js"
6
-
7
- // ---------------------------------------------------------------------------
8
- // Constants
9
- // ---------------------------------------------------------------------------
10
-
11
- /** Where the JSON index lives */
12
- export const SKILLS_INDEX_PATH = (): string => {
13
- const home = os.homedir()
14
- const cacheDir = path.join(home, ".cache", "otto")
15
- fs.mkdirSync(cacheDir, { recursive: true })
16
- return path.join(cacheDir, "skills-index.json")
17
- }
18
-
19
- /** Where installed skills go */
20
- export const OPENCODE_SKILLS_DIR = (): string => {
21
- return path.join(OPENCODE_CONFIG_DIR(), "skills")
22
- }
23
-
24
- /** Default skill repos — curated, known to have quality skills */
25
- export const DEFAULT_SKILL_REPOS: string[] = [
26
- "otto-assistant/bridge",
27
- "otto-assistant/skills",
28
- "anthropics/skills",
29
- "vercel-labs/agent-skills",
30
- "microsoft/skills",
31
- "obra/superpowers",
32
- ]
33
-
34
- // ---------------------------------------------------------------------------
35
- // Types
36
- // ---------------------------------------------------------------------------
37
-
38
- export interface SkillMeta {
39
- name: string
40
- description: string
41
- metadata?: Record<string, string>
42
- }
43
-
44
- /** One entry in the skills index */
45
- export interface SkillIndexEntry {
46
- name: string
47
- description: string
48
- metadata?: Record<string, string>
49
- source: string // e.g. "anthropics/skills"
50
- path: string // e.g. "skills/frontend-design"
51
- }
52
-
53
- /** The full on-disk index */
54
- export interface SkillsIndex {
55
- version: number
56
- updated: string // ISO timestamp
57
- repos: Record<string, {
58
- fetched: string // ISO timestamp
59
- skills: SkillIndexEntry[]
60
- }>
61
- }
62
-
63
- export type RepoSyncResult = "updated" | "cached" | "offline"
64
-
65
- // ---------------------------------------------------------------------------
66
- // SKILL.md Parser (unchanged)
67
- // ---------------------------------------------------------------------------
68
-
69
- /**
70
- * Parses YAML frontmatter from skill markdown content.
71
- * Returns null if no valid frontmatter or missing required fields (name, description).
72
- */
73
- export function parseSkillMd(content: string): SkillMeta | null {
74
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
75
- if (!match) return null
76
-
77
- const frontmatter = match[1]
78
-
79
- const nameMatch = frontmatter.match(/^name:\s*(.+)$/m)
80
- if (!nameMatch) return null
81
- const name = nameMatch[1].trim()
82
-
83
- const descMatch = frontmatter.match(/^description:\s*(.+)$/m)
84
- if (!descMatch) return null
85
- const description = descMatch[1].trim()
86
-
87
- const meta: Record<string, string> = {}
88
- const metaMatch = frontmatter.match(/^metadata:\s*\r?\n((?:\s{2,}\S.*\r?\n?)*)/m)
89
- if (metaMatch) {
90
- const metaBlock = metaMatch[1]
91
- const lines = metaBlock.split(/\r?\n/).filter((l) => l.trim().length > 0)
92
- for (const line of lines) {
93
- const kv = line.trim().match(/^(\S+):\s*(.+)$/)
94
- if (kv) {
95
- let val = kv[2].trim()
96
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
97
- val = val.slice(1, -1)
98
- }
99
- meta[kv[1]] = val
100
- }
101
- }
102
- }
103
-
104
- return {
105
- name,
106
- description,
107
- ...(Object.keys(meta).length > 0 ? { metadata: meta } : {}),
108
- }
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // Local skill discovery
113
- // ---------------------------------------------------------------------------
114
-
115
- /** Lists installed skills from OPENCODE_SKILLS_DIR. */
116
- export function listInstalledSkills(baseDir?: string): SkillMeta[] {
117
- const dir = baseDir ?? OPENCODE_SKILLS_DIR()
118
- return discoverSkills(dir)
119
- }
120
-
121
- /** Internal: scan dir subdirectories and parse skill markdown files. */
122
- function discoverSkills(dir: string): SkillMeta[] {
123
- const results: SkillMeta[] = []
124
-
125
- let entries: string[]
126
- try {
127
- entries = fs.readdirSync(dir)
128
- } catch {
129
- return results
130
- }
131
-
132
- for (const entry of entries) {
133
- const skillMdPath = path.join(dir, entry, "SKILL.md")
134
- try {
135
- const content = fs.readFileSync(skillMdPath, "utf-8")
136
- const meta = parseSkillMd(content)
137
- if (meta) {
138
- results.push(meta)
139
- }
140
- } catch {
141
- // skip invalid or missing SKILL.md
142
- }
143
- }
144
-
145
- return results
146
- }
147
-
148
- // ---------------------------------------------------------------------------
149
- // Remove installed skill
150
- // ---------------------------------------------------------------------------
151
-
152
- /**
153
- * Removes an installed skill directory.
154
- * Returns true if removed, false if not found.
155
- */
156
- export function removeSkill(name: string, targetDir?: string): boolean {
157
- const target = targetDir ?? OPENCODE_SKILLS_DIR()
158
- const skillDir = path.join(target, name)
159
-
160
- if (!fs.existsSync(skillDir)) return false
161
-
162
- fs.rmSync(skillDir, { recursive: true, force: true })
163
- return true
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // GitHub API (via gh CLI)
168
- // ---------------------------------------------------------------------------
169
-
170
- /**
171
- * Call GitHub API using `gh api`. Returns parsed JSON.
172
- * Throws on network/auth errors or non-2xx responses.
173
- */
174
- export function ghApi(endpoint: string): unknown {
175
- const result = execFileSync("gh", ["api", endpoint], {
176
- encoding: "utf-8",
177
- stdio: ["pipe", "pipe", "pipe"],
178
- timeout: 15_000,
179
- })
180
- return JSON.parse(result)
181
- }
182
-
183
- /**
184
- * Fetch directory listing from a GitHub repo path.
185
- * Returns array of {name, type, path} entries, or empty array on error.
186
- */
187
- export function fetchRepoDir(ownerRepo: string, dirPath: string): Array<{ name: string; type: string; path: string }> {
188
- try {
189
- const result = ghApi(`repos/${ownerRepo}/contents/${dirPath}`)
190
- if (!Array.isArray(result)) return []
191
- return result
192
- .filter((item: any) => typeof item.name === "string" && typeof item.type === "string")
193
- .map((item: any) => ({ name: item.name, type: item.type, path: item.path }))
194
- } catch {
195
- return []
196
- }
197
- }
198
-
199
- /**
200
- * Fetch a single file content from a GitHub repo.
201
- * Returns decoded UTF-8 string, or null on error.
202
- */
203
- export function fetchRepoFile(ownerRepo: string, filePath: string): string | null {
204
- try {
205
- const result = ghApi(`repos/${ownerRepo}/contents/${filePath}`) as { content?: string; encoding?: string }
206
- if (!result.content || result.encoding !== "base64") return null
207
- return Buffer.from(result.content, "base64").toString("utf-8")
208
- } catch {
209
- return null
210
- }
211
- }
212
-
213
- // ---------------------------------------------------------------------------
214
- // Skills Index (JSON cache)
215
- // ---------------------------------------------------------------------------
216
-
217
- /** Load index from disk. Returns empty index if file doesn't exist. */
218
- export function loadSkillsIndex(indexPath?: string): SkillsIndex {
219
- const p = indexPath ?? SKILLS_INDEX_PATH()
220
- try {
221
- const raw = fs.readFileSync(p, "utf-8")
222
- return JSON.parse(raw) as SkillsIndex
223
- } catch {
224
- return { version: 1, updated: "", repos: {} }
225
- }
226
- }
227
-
228
- /** Save index to disk. */
229
- export function saveSkillsIndex(index: SkillsIndex, indexPath?: string): void {
230
- const p = indexPath ?? SKILLS_INDEX_PATH()
231
- fs.mkdirSync(path.dirname(p), { recursive: true })
232
- fs.writeFileSync(p, JSON.stringify(index, null, 2), "utf-8")
233
- }
234
-
235
- /** Check if a fetched timestamp is older than maxAgeHours. */
236
- export function isIndexStale(fetchedAt: string, maxAgeHours = 24): boolean {
237
- if (!fetchedAt) return true
238
- const fetched = new Date(fetchedAt).getTime()
239
- return Date.now() - fetched > maxAgeHours * 60 * 60 * 1000
240
- }
241
-
242
- // ---------------------------------------------------------------------------
243
- // Index a single repo via GitHub API
244
- // ---------------------------------------------------------------------------
245
-
246
- /**
247
- * Index all skills from a single GitHub repo by fetching directory listings + SKILL.md frontmatter.
248
- * Works with repos that have skills at `skills/<name>/SKILL.md` (flat or nested one level).
249
- * Returns array of SkillIndexEntry.
250
- */
251
- export function fetchRepoSkillsIndex(ownerRepo: string): SkillIndexEntry[] {
252
- const entries: SkillIndexEntry[] = []
253
-
254
- const topDirs = fetchRepoDir(ownerRepo, "skills")
255
-
256
- for (const item of topDirs) {
257
- if (item.type !== "dir") continue
258
-
259
- // Try flat: skills/<name>/SKILL.md
260
- const skillMd = fetchRepoFile(ownerRepo, `${item.path}/SKILL.md`)
261
- if (skillMd) {
262
- const meta = parseSkillMd(skillMd)
263
- if (meta) {
264
- entries.push({
265
- name: meta.name,
266
- description: meta.description,
267
- metadata: meta.metadata,
268
- source: ownerRepo,
269
- path: item.path,
270
- })
271
- continue
272
- }
273
- }
274
-
275
- // Try nested: skills/<category>/<name>/SKILL.md (microsoft/skills pattern)
276
- const subDirs = fetchRepoDir(ownerRepo, item.path)
277
- for (const sub of subDirs) {
278
- if (sub.type !== "dir") continue
279
- const subMd = fetchRepoFile(ownerRepo, `${sub.path}/SKILL.md`)
280
- if (subMd) {
281
- const meta = parseSkillMd(subMd)
282
- if (meta) {
283
- entries.push({
284
- name: meta.name,
285
- description: meta.description,
286
- metadata: meta.metadata,
287
- source: ownerRepo,
288
- path: sub.path,
289
- })
290
- }
291
- }
292
- }
293
- }
294
-
295
- return entries
296
- }
297
-
298
- // ---------------------------------------------------------------------------
299
- // Search
300
- // ---------------------------------------------------------------------------
301
-
302
- /**
303
- * Search indexed skills by query. Matches against name and description (case-insensitive).
304
- */
305
- export function searchSkills(query: string, indexPath?: string): SkillIndexEntry[] {
306
- const index = loadSkillsIndex(indexPath)
307
- const q = query.toLowerCase()
308
- const results: SkillIndexEntry[] = []
309
-
310
- for (const repoData of Object.values(index.repos)) {
311
- for (const skill of repoData.skills) {
312
- if (
313
- skill.name.toLowerCase().includes(q) ||
314
- skill.description.toLowerCase().includes(q)
315
- ) {
316
- results.push(skill)
317
- }
318
- }
319
- }
320
-
321
- return results
322
- }
323
-
324
- /**
325
- * Get all indexed skills across all repos.
326
- */
327
- export function getAllIndexedSkills(indexPath?: string): SkillIndexEntry[] {
328
- const index = loadSkillsIndex(indexPath)
329
- const all: SkillIndexEntry[] = []
330
- for (const repoData of Object.values(index.repos)) {
331
- all.push(...repoData.skills)
332
- }
333
- return all
334
- }
335
-
336
- // ---------------------------------------------------------------------------
337
- // Index refresh
338
- // ---------------------------------------------------------------------------
339
-
340
- /**
341
- * Get configured repos: DEFAULT_SKILL_REPOS + any user-added repos.
342
- */
343
- export function getConfiguredRepos(): string[] {
344
- return [...DEFAULT_SKILL_REPOS]
345
- }
346
-
347
- /**
348
- * Refresh the skills index by fetching all configured repos.
349
- * Skips repos that were fetched recently (within maxAgeHours).
350
- * Returns the number of repos refreshed.
351
- */
352
- export function ensureSkillsIndex(maxAgeHours = 24, indexPath?: string): { refreshed: number; total: number } {
353
- const index = loadSkillsIndex(indexPath)
354
- const repos = getConfiguredRepos()
355
- let refreshed = 0
356
-
357
- for (const repo of repos) {
358
- const existing = index.repos[repo]
359
- if (existing && !isIndexStale(existing.fetched, maxAgeHours)) {
360
- continue // still fresh
361
- }
362
-
363
- try {
364
- const skills = fetchRepoSkillsIndex(repo)
365
- index.repos[repo] = {
366
- fetched: new Date().toISOString(),
367
- skills,
368
- }
369
- refreshed++
370
- } catch {
371
- if (!existing) {
372
- index.repos[repo] = { fetched: "", skills: [] }
373
- }
374
- }
375
- }
376
-
377
- index.updated = new Date().toISOString()
378
- saveSkillsIndex(index, indexPath)
379
- return { refreshed, total: repos.length }
380
- }
381
-
382
- // ---------------------------------------------------------------------------
383
- // Install from index (API-based)
384
- // ---------------------------------------------------------------------------
385
-
386
- /**
387
- * Install a skill from the index by fetching its files from GitHub API.
388
- * Looks up the skill in the index, fetches SKILL.md (and any supporting files),
389
- * writes them to the target directory.
390
- *
391
- * Returns true if installed, false if not found in index or fetch failed.
392
- */
393
- export function installSkillFromIndex(
394
- skillName: string,
395
- targetDir?: string,
396
- indexPath?: string,
397
- ): boolean {
398
- const target = targetDir ?? OPENCODE_SKILLS_DIR()
399
- const index = loadSkillsIndex(indexPath)
400
-
401
- // Find skill in index
402
- let entry: SkillIndexEntry | undefined
403
- for (const repoData of Object.values(index.repos)) {
404
- entry = repoData.skills.find((s) => s.name === skillName)
405
- if (entry) break
406
- }
407
-
408
- if (!entry) return false
409
-
410
- // Fetch SKILL.md from GitHub
411
- const skillMd = fetchRepoFile(entry.source, `${entry.path}/SKILL.md`)
412
- if (!skillMd) return false
413
-
414
- // Validate it parses
415
- const meta = parseSkillMd(skillMd)
416
- if (!meta) return false
417
-
418
- // Write SKILL.md
419
- const destDir = path.join(target, skillName)
420
- fs.mkdirSync(destDir, { recursive: true })
421
- fs.writeFileSync(path.join(destDir, "SKILL.md"), skillMd, "utf-8")
422
-
423
- // Fetch any supporting files in the same directory
424
- const dirContents = fetchRepoDir(entry.source, entry.path)
425
- for (const item of dirContents) {
426
- if (item.name === "SKILL.md") continue
427
- if (item.type === "file") {
428
- const fileContent = fetchRepoFile(entry.source, item.path)
429
- if (fileContent) {
430
- fs.writeFileSync(path.join(destDir, item.name), fileContent, "utf-8")
431
- }
432
- } else if (item.type === "dir") {
433
- fetchDirRecursive(entry.source, item.path, path.join(destDir, item.name))
434
- }
435
- }
436
-
437
- return true
438
- }
439
-
440
- /**
441
- * Install a baseline list of skills without removing user-installed ones.
442
- */
443
- export function installSkillsBaseline(
444
- skillNames: string[],
445
- targetDir?: string,
446
- indexPath?: string,
447
- ): { installed: string[]; alreadyPresent: string[]; failed: string[] } {
448
- const installed: string[] = []
449
- const alreadyPresent: string[] = []
450
- const failed: string[] = []
451
-
452
- const target = targetDir ?? OPENCODE_SKILLS_DIR()
453
- fs.mkdirSync(target, { recursive: true })
454
-
455
- const existing = new Set(listInstalledSkills(target).map((s) => s.name))
456
-
457
- for (const name of skillNames) {
458
- if (existing.has(name)) {
459
- alreadyPresent.push(name)
460
- continue
461
- }
462
-
463
- const ok = installSkillFromIndex(name, target, indexPath) || installSkillFromBuiltIn(name, target)
464
- if (ok) {
465
- installed.push(name)
466
- existing.add(name)
467
- } else {
468
- failed.push(name)
469
- }
470
- }
471
-
472
- return { installed, alreadyPresent, failed }
473
- }
474
-
475
- function installSkillFromBuiltIn(skillName: string, targetDir: string): boolean {
476
- const builtInRoots = [
477
- "/usr/local/lib/node_modules/@otto-assistant/bridge/skills",
478
- "/usr/lib/node_modules/@otto-assistant/bridge/skills",
479
- "/usr/lib/node_modules/kimaki/skills",
480
- ]
481
-
482
- for (const root of builtInRoots) {
483
- const sourceDir = path.join(root, skillName)
484
- const sourceSkillMd = path.join(sourceDir, "SKILL.md")
485
- if (!fs.existsSync(sourceSkillMd)) continue
486
-
487
- const destDir = path.join(targetDir, skillName)
488
- fs.mkdirSync(path.dirname(destDir), { recursive: true })
489
- fs.cpSync(sourceDir, destDir, { recursive: true })
490
- return true
491
- }
492
-
493
- return false
494
- }
495
-
496
- /**
497
- * Recursively fetch a directory from GitHub and write to local disk.
498
- */
499
- function fetchDirRecursive(ownerRepo: string, ghPath: string, localPath: string): void {
500
- fs.mkdirSync(localPath, { recursive: true })
501
- const contents = fetchRepoDir(ownerRepo, ghPath)
502
- for (const item of contents) {
503
- if (item.type === "file") {
504
- const content = fetchRepoFile(ownerRepo, item.path)
505
- if (content) {
506
- fs.writeFileSync(path.join(localPath, item.name), content, "utf-8")
507
- }
508
- } else if (item.type === "dir") {
509
- fetchDirRecursive(ownerRepo, item.path, path.join(localPath, item.name))
510
- }
511
- }
512
- }
package/src/sync.ts DELETED
@@ -1,53 +0,0 @@
1
- import { execSync } from "node:child_process"
2
- import { UPSTREAM_REPOS } from "./manifest.js"
3
-
4
- interface SyncTarget {
5
- repo: string
6
- upstream: string
7
- branch: string
8
- }
9
-
10
- function getSyncTargets(): SyncTarget[] {
11
- return Object.entries(UPSTREAM_REPOS).map(([_pkgName, info]) => ({
12
- repo: info.repo,
13
- upstream: info.upstream,
14
- branch: "main",
15
- }))
16
- }
17
-
18
- export async function syncUpstreams(): Promise<void> {
19
- const targets = getSyncTargets()
20
-
21
- if (targets.length === 0) {
22
- console.log("No upstream repos configured for sync.")
23
- return
24
- }
25
-
26
- // Check gh CLI is available
27
- try {
28
- execSync("gh --version", { stdio: "pipe" })
29
- } catch {
30
- console.error("Error: gh CLI is required for sync. Install: https://cli.github.com/")
31
- process.exit(1)
32
- }
33
-
34
- console.log("Triggering upstream sync for all forked repos:\n")
35
-
36
- for (const target of targets) {
37
- console.log(` ${target.repo} ← ${target.upstream}`)
38
- try {
39
- execSync(
40
- `gh workflow run sync-upstream.yml --repo ${target.repo} --ref ${target.branch}`,
41
- { stdio: "pipe" },
42
- )
43
- console.log(` ✓ Sync triggered`)
44
- } catch (err: unknown) {
45
- const msg = err instanceof Error ? err.message : String(err)
46
- console.error(` ✗ Failed: ${msg}`)
47
- }
48
- }
49
-
50
- console.log("\nSync workflows triggered. Check status with: gh run list --repo <repo>")
51
- }
52
-
53
- export { getSyncTargets, UPSTREAM_REPOS }
@@ -1,49 +0,0 @@
1
- import fs from "node:fs"
2
- import os from "node:os"
3
- import path from "node:path"
4
- import { describe, expect, it } from "vitest"
5
- import {
6
- deriveComposeProjectName,
7
- ensureTenantMemoryLayout,
8
- ensureTenantScaffold,
9
- resolveTenantImage,
10
- resolveTenantMode,
11
- } from "./tenant.js"
12
-
13
- describe("tenant", () => {
14
- it("uses otto-<folder_name> default", () => {
15
- expect(deriveComposeProjectName("/tmp/my-tenant")).toBe("otto-my-tenant")
16
- })
17
-
18
- it("uses image from compose.yml when env override missing", () => {
19
- const resolved = resolveTenantImage({
20
- composeImage: "otto-assistant/otto:stable",
21
- envImage: undefined,
22
- })
23
- expect(resolved).toBe("otto-assistant/otto:stable")
24
- })
25
-
26
- it("defaults to safe mode when OTTO_MODE is unset", () => {
27
- expect(resolveTenantMode(undefined)).toBe("safe")
28
- })
29
-
30
- it("creates required memory files and mempalace dir", () => {
31
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "otto-memory-"))
32
- const report = ensureTenantMemoryLayout(root)
33
-
34
- expect(report.created).toContain("AGENTS.md")
35
- expect(report.created).toContain("soul.md")
36
- expect(report.created).toContain("persona.md")
37
- expect(report.created).toContain("mempalace/")
38
- })
39
-
40
- it("creates compose scaffold for tenant", () => {
41
- const tenantPath = fs.mkdtempSync(path.join(os.tmpdir(), "otto-tenant-"))
42
- const report = ensureTenantScaffold(tenantPath)
43
-
44
- expect(report.created).toContain("compose.yml")
45
- expect(fs.existsSync(path.join(tenantPath, "compose.yml"))).toBe(true)
46
- expect(fs.existsSync(path.join(tenantPath, "memory", "AGENTS.md"))).toBe(true)
47
- expect(fs.existsSync(path.join(tenantPath, "projects"))).toBe(true)
48
- })
49
- })