shortcutxl 0.2.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 (423) hide show
  1. package/README.md +59 -0
  2. package/agent-docs/README.md +397 -0
  3. package/agent-docs/docs/compaction.md +390 -0
  4. package/agent-docs/docs/custom-provider.md +580 -0
  5. package/agent-docs/docs/development.md +69 -0
  6. package/agent-docs/docs/extensions.md +1971 -0
  7. package/agent-docs/docs/json.md +79 -0
  8. package/agent-docs/docs/keybindings.md +174 -0
  9. package/agent-docs/docs/models.md +293 -0
  10. package/agent-docs/docs/packages.md +209 -0
  11. package/agent-docs/docs/prompt-templates.md +67 -0
  12. package/agent-docs/docs/providers.md +186 -0
  13. package/agent-docs/docs/rpc.md +1317 -0
  14. package/agent-docs/docs/sdk.md +962 -0
  15. package/agent-docs/docs/session.md +412 -0
  16. package/agent-docs/docs/settings.md +223 -0
  17. package/agent-docs/docs/shell-aliases.md +13 -0
  18. package/agent-docs/docs/skills.md +231 -0
  19. package/agent-docs/docs/terminal-setup.md +70 -0
  20. package/agent-docs/docs/termux.md +127 -0
  21. package/agent-docs/docs/themes.md +295 -0
  22. package/agent-docs/docs/tree.md +219 -0
  23. package/agent-docs/docs/tui.md +887 -0
  24. package/agent-docs/docs/windows.md +17 -0
  25. package/agent-docs/examples/README.md +25 -0
  26. package/agent-docs/examples/extensions/README.md +205 -0
  27. package/agent-docs/examples/extensions/antigravity-image-gen.ts +447 -0
  28. package/agent-docs/examples/extensions/auto-commit-on-exit.ts +49 -0
  29. package/agent-docs/examples/extensions/bash-spawn-hook.ts +30 -0
  30. package/agent-docs/examples/extensions/bookmark.ts +50 -0
  31. package/agent-docs/examples/extensions/built-in-tool-renderer.ts +256 -0
  32. package/agent-docs/examples/extensions/claude-rules.ts +86 -0
  33. package/agent-docs/examples/extensions/commands.ts +75 -0
  34. package/agent-docs/examples/extensions/confirm-destructive.ts +59 -0
  35. package/agent-docs/examples/extensions/custom-compaction.ts +126 -0
  36. package/agent-docs/examples/extensions/custom-footer.ts +63 -0
  37. package/agent-docs/examples/extensions/custom-header.ts +73 -0
  38. package/agent-docs/examples/extensions/custom-provider-anthropic/index.ts +660 -0
  39. package/agent-docs/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  40. package/agent-docs/examples/extensions/custom-provider-anthropic/package.json +19 -0
  41. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +362 -0
  42. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  43. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +88 -0
  44. package/agent-docs/examples/extensions/custom-provider-qwen-cli/index.ts +349 -0
  45. package/agent-docs/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  46. package/agent-docs/examples/extensions/dirty-repo-guard.ts +56 -0
  47. package/agent-docs/examples/extensions/doom-overlay/README.md +46 -0
  48. package/agent-docs/examples/extensions/doom-overlay/doom/build.sh +152 -0
  49. package/agent-docs/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  50. package/agent-docs/examples/extensions/doom-overlay/doom-component.ts +133 -0
  51. package/agent-docs/examples/extensions/doom-overlay/doom-engine.ts +186 -0
  52. package/agent-docs/examples/extensions/doom-overlay/doom-keys.ts +108 -0
  53. package/agent-docs/examples/extensions/doom-overlay/index.ts +74 -0
  54. package/agent-docs/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  55. package/agent-docs/examples/extensions/dynamic-resources/SKILL.md +8 -0
  56. package/agent-docs/examples/extensions/dynamic-resources/dynamic.json +79 -0
  57. package/agent-docs/examples/extensions/dynamic-resources/dynamic.md +5 -0
  58. package/agent-docs/examples/extensions/dynamic-resources/index.ts +15 -0
  59. package/agent-docs/examples/extensions/dynamic-tools.ts +77 -0
  60. package/agent-docs/examples/extensions/event-bus.ts +43 -0
  61. package/agent-docs/examples/extensions/file-trigger.ts +41 -0
  62. package/agent-docs/examples/extensions/git-checkpoint.ts +53 -0
  63. package/agent-docs/examples/extensions/handoff.ts +155 -0
  64. package/agent-docs/examples/extensions/hello.ts +25 -0
  65. package/agent-docs/examples/extensions/inline-bash.ts +94 -0
  66. package/agent-docs/examples/extensions/input-transform.ts +43 -0
  67. package/agent-docs/examples/extensions/interactive-shell.ts +209 -0
  68. package/agent-docs/examples/extensions/mac-system-theme.ts +47 -0
  69. package/agent-docs/examples/extensions/message-renderer.ts +59 -0
  70. package/agent-docs/examples/extensions/minimal-mode.ts +430 -0
  71. package/agent-docs/examples/extensions/modal-editor.ts +90 -0
  72. package/agent-docs/examples/extensions/model-status.ts +31 -0
  73. package/agent-docs/examples/extensions/notify.ts +55 -0
  74. package/agent-docs/examples/extensions/overlay-qa-tests.ts +936 -0
  75. package/agent-docs/examples/extensions/overlay-test.ts +159 -0
  76. package/agent-docs/examples/extensions/permission-gate.ts +37 -0
  77. package/agent-docs/examples/extensions/pirate.ts +47 -0
  78. package/agent-docs/examples/extensions/plan-mode/README.md +65 -0
  79. package/agent-docs/examples/extensions/plan-mode/index.ts +363 -0
  80. package/agent-docs/examples/extensions/plan-mode/utils.ts +173 -0
  81. package/agent-docs/examples/extensions/preset.ts +418 -0
  82. package/agent-docs/examples/extensions/protected-paths.ts +30 -0
  83. package/agent-docs/examples/extensions/qna.ts +122 -0
  84. package/agent-docs/examples/extensions/question.ts +278 -0
  85. package/agent-docs/examples/extensions/questionnaire.ts +440 -0
  86. package/agent-docs/examples/extensions/rainbow-editor.ts +90 -0
  87. package/agent-docs/examples/extensions/reload-runtime.ts +37 -0
  88. package/agent-docs/examples/extensions/rpc-demo.ts +124 -0
  89. package/agent-docs/examples/extensions/sandbox/index.ts +324 -0
  90. package/agent-docs/examples/extensions/sandbox/package-lock.json +92 -0
  91. package/agent-docs/examples/extensions/sandbox/package.json +19 -0
  92. package/agent-docs/examples/extensions/send-user-message.ts +97 -0
  93. package/agent-docs/examples/extensions/session-name.ts +27 -0
  94. package/agent-docs/examples/extensions/shutdown-command.ts +69 -0
  95. package/agent-docs/examples/extensions/snake.ts +343 -0
  96. package/agent-docs/examples/extensions/space-invaders.ts +566 -0
  97. package/agent-docs/examples/extensions/ssh.ts +233 -0
  98. package/agent-docs/examples/extensions/status-line.ts +40 -0
  99. package/agent-docs/examples/extensions/subagent/README.md +172 -0
  100. package/agent-docs/examples/extensions/subagent/agents/planner.md +37 -0
  101. package/agent-docs/examples/extensions/subagent/agents/reviewer.md +35 -0
  102. package/agent-docs/examples/extensions/subagent/agents/scout.md +50 -0
  103. package/agent-docs/examples/extensions/subagent/agents/worker.md +24 -0
  104. package/agent-docs/examples/extensions/subagent/agents.ts +130 -0
  105. package/agent-docs/examples/extensions/subagent/index.ts +1068 -0
  106. package/agent-docs/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  107. package/agent-docs/examples/extensions/subagent/prompts/implement.md +10 -0
  108. package/agent-docs/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  109. package/agent-docs/examples/extensions/summarize.ts +206 -0
  110. package/agent-docs/examples/extensions/system-prompt-header.ts +17 -0
  111. package/agent-docs/examples/extensions/timed-confirm.ts +72 -0
  112. package/agent-docs/examples/extensions/titlebar-spinner.ts +58 -0
  113. package/agent-docs/examples/extensions/todo.ts +314 -0
  114. package/agent-docs/examples/extensions/tool-override.ts +146 -0
  115. package/agent-docs/examples/extensions/tools.ts +145 -0
  116. package/agent-docs/examples/extensions/trigger-compact.ts +40 -0
  117. package/agent-docs/examples/extensions/truncated-tool.ts +194 -0
  118. package/agent-docs/examples/extensions/widget-placement.ts +17 -0
  119. package/agent-docs/examples/extensions/with-deps/index.ts +37 -0
  120. package/agent-docs/examples/extensions/with-deps/package-lock.json +31 -0
  121. package/agent-docs/examples/extensions/with-deps/package.json +22 -0
  122. package/agent-docs/examples/rpc-extension-ui.ts +654 -0
  123. package/agent-docs/examples/sdk/01-minimal.ts +22 -0
  124. package/agent-docs/examples/sdk/02-custom-model.ts +48 -0
  125. package/agent-docs/examples/sdk/03-custom-prompt.ts +55 -0
  126. package/agent-docs/examples/sdk/04-skills.ts +53 -0
  127. package/agent-docs/examples/sdk/05-tools.ts +56 -0
  128. package/agent-docs/examples/sdk/06-extensions.ts +88 -0
  129. package/agent-docs/examples/sdk/07-context-files.ts +40 -0
  130. package/agent-docs/examples/sdk/08-prompt-templates.ts +47 -0
  131. package/agent-docs/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  132. package/agent-docs/examples/sdk/10-settings.ts +54 -0
  133. package/agent-docs/examples/sdk/11-sessions.ts +48 -0
  134. package/agent-docs/examples/sdk/12-full-control.ts +82 -0
  135. package/agent-docs/examples/sdk/README.md +144 -0
  136. package/agent-docs/xll-skill.md +61 -0
  137. package/agent-docs/xll-spec.md +110 -0
  138. package/dist/cli/args.js +290 -0
  139. package/dist/cli/config-selector.js +31 -0
  140. package/dist/cli/file-processor.js +79 -0
  141. package/dist/cli/list-models.js +92 -0
  142. package/dist/cli/package-commands.js +210 -0
  143. package/dist/cli/report-settings-errors.js +11 -0
  144. package/dist/cli/session-picker.js +34 -0
  145. package/dist/cli.js +19 -0
  146. package/dist/config.js +288 -0
  147. package/dist/core/abort.js +15 -0
  148. package/dist/core/agent-loop.js +352 -0
  149. package/dist/core/agent-session.js +2019 -0
  150. package/dist/core/agent.js +410 -0
  151. package/dist/core/auth-storage.js +456 -0
  152. package/dist/core/bash-executor.js +222 -0
  153. package/dist/core/compaction/branch-summarization.js +242 -0
  154. package/dist/core/compaction/compaction.js +610 -0
  155. package/dist/core/compaction/index.js +7 -0
  156. package/dist/core/compaction/utils.js +139 -0
  157. package/dist/core/defaults.js +6 -0
  158. package/dist/core/diagnostics.js +2 -0
  159. package/dist/core/event-bus.js +25 -0
  160. package/dist/core/exec.js +71 -0
  161. package/dist/core/export-html/ansi-to-html.js +256 -0
  162. package/dist/core/export-html/index.js +238 -0
  163. package/dist/core/export-html/session-view-model.js +342 -0
  164. package/dist/core/export-html/template.css +1110 -0
  165. package/dist/core/export-html/template.html +76 -0
  166. package/dist/core/export-html/template.js +1990 -0
  167. package/dist/core/export-html/tool-renderer.js +63 -0
  168. package/dist/core/export-html/vendor/highlight.min.js +7725 -0
  169. package/dist/core/export-html/vendor/marked.min.js +1803 -0
  170. package/dist/core/extensions/index.js +9 -0
  171. package/dist/core/extensions/loader.js +422 -0
  172. package/dist/core/extensions/runner.js +651 -0
  173. package/dist/core/extensions/types.js +35 -0
  174. package/dist/core/extensions/wrapper.js +102 -0
  175. package/dist/core/footer-data-provider.js +162 -0
  176. package/dist/core/index.js +9 -0
  177. package/dist/core/keybindings.js +153 -0
  178. package/dist/core/messages.js +133 -0
  179. package/dist/core/model-registry.js +539 -0
  180. package/dist/core/model-resolver.js +370 -0
  181. package/dist/core/package-manager.js +1485 -0
  182. package/dist/core/prompt-templates.js +253 -0
  183. package/dist/core/resolve-config-value.js +59 -0
  184. package/dist/core/resource-loader.js +700 -0
  185. package/dist/core/sdk.js +197 -0
  186. package/dist/core/session-bash.js +99 -0
  187. package/dist/core/session-compaction.js +165 -0
  188. package/dist/core/session-manager.js +1153 -0
  189. package/dist/core/session-models.js +99 -0
  190. package/dist/core/session-retry.js +155 -0
  191. package/dist/core/settings-manager.js +572 -0
  192. package/dist/core/skills.js +382 -0
  193. package/dist/core/slash-commands.js +31 -0
  194. package/dist/core/system-prompt.js +161 -0
  195. package/dist/core/theme.js +770 -0
  196. package/dist/core/timings.js +26 -0
  197. package/dist/core/tools/bash.js +258 -0
  198. package/dist/core/tools/edit-diff.js +245 -0
  199. package/dist/core/tools/edit.js +148 -0
  200. package/dist/core/tools/find.js +208 -0
  201. package/dist/core/tools/grep.js +246 -0
  202. package/dist/core/tools/index.js +67 -0
  203. package/dist/core/tools/ls.js +123 -0
  204. package/dist/core/tools/path-utils.js +81 -0
  205. package/dist/core/tools/read.js +160 -0
  206. package/dist/core/tools/truncate.js +70 -0
  207. package/dist/core/tools/write.js +82 -0
  208. package/dist/custom/agents/action.js +13 -0
  209. package/dist/custom/agents/document-reader.js +70 -0
  210. package/dist/custom/agents/general.js +26 -0
  211. package/dist/custom/agents/index.js +49 -0
  212. package/dist/custom/agents/installation.js +13 -0
  213. package/dist/custom/agents/types.js +7 -0
  214. package/dist/custom/auth/refresh-timer.js +33 -0
  215. package/dist/custom/auth/shortcut-oauth.js +145 -0
  216. package/dist/custom/constants.js +21 -0
  217. package/dist/custom/context/workbook-summary.js +73 -0
  218. package/dist/custom/credits/shortcut-credits.js +29 -0
  219. package/dist/custom/cron/cron-daemon-entry.js +18 -0
  220. package/dist/custom/cron/daemon-ipc.js +131 -0
  221. package/dist/custom/cron/daemon.js +224 -0
  222. package/dist/custom/cron/jobs.js +226 -0
  223. package/dist/custom/cron/run-log.js +51 -0
  224. package/dist/custom/cron/schedule.js +72 -0
  225. package/dist/custom/cron/status-line.js +98 -0
  226. package/dist/custom/cron/store.js +87 -0
  227. package/dist/custom/cron/types.js +8 -0
  228. package/dist/custom/dev/index.js +59 -0
  229. package/dist/custom/dev/trace-export.js +58 -0
  230. package/dist/custom/ensure-excel.js +63 -0
  231. package/dist/custom/excel-config.js +36 -0
  232. package/dist/custom/preflight.js +422 -0
  233. package/dist/custom/prompts/action.js +100 -0
  234. package/dist/custom/prompts/api.js +66 -0
  235. package/dist/custom/prompts/installation.js +124 -0
  236. package/dist/custom/prompts/shared.js +138 -0
  237. package/dist/custom/providers/llm-usage.js +42 -0
  238. package/dist/custom/providers/message-converter.js +74 -0
  239. package/dist/custom/providers/provider-ids.js +9 -0
  240. package/dist/custom/providers/register-openai-codex-provider.js +27 -0
  241. package/dist/custom/providers/register-shortcut-provider.js +52 -0
  242. package/dist/custom/providers/shortcut-invoke.js +117 -0
  243. package/dist/custom/providers/shortcut-stream.js +252 -0
  244. package/dist/custom/providers/sse-protocol.js +38 -0
  245. package/dist/custom/sync-xll.js +130 -0
  246. package/dist/custom/tools/cron.js +413 -0
  247. package/dist/custom/tools/excel-exec.js +167 -0
  248. package/dist/custom/tools/excel-range.js +50 -0
  249. package/dist/custom/tools/llm-analysis.js +265 -0
  250. package/dist/custom/tools/render-helpers.js +38 -0
  251. package/dist/custom/tools/switch-mode.js +94 -0
  252. package/dist/custom/tools/task/agents.js +6 -0
  253. package/dist/custom/tools/task/index.js +8 -0
  254. package/dist/custom/tools/task/render.js +348 -0
  255. package/dist/custom/tools/task/subprocess.js +320 -0
  256. package/dist/custom/tools/task/task.js +205 -0
  257. package/dist/custom/tools/todo-list.js +195 -0
  258. package/dist/custom/tracing/session-upload.js +93 -0
  259. package/dist/index.js +45 -0
  260. package/dist/main.js +613 -0
  261. package/dist/migrations.js +265 -0
  262. package/dist/modes/index.js +8 -0
  263. package/dist/modes/interactive/components/armin.js +337 -0
  264. package/dist/modes/interactive/components/assistant-message.js +94 -0
  265. package/dist/modes/interactive/components/bash-execution.js +171 -0
  266. package/dist/modes/interactive/components/bordered-loader.js +51 -0
  267. package/dist/modes/interactive/components/branch-summary-message.js +45 -0
  268. package/dist/modes/interactive/components/compaction-summary-message.js +46 -0
  269. package/dist/modes/interactive/components/config-selector.js +488 -0
  270. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  271. package/dist/modes/interactive/components/custom-editor.js +93 -0
  272. package/dist/modes/interactive/components/custom-message.js +81 -0
  273. package/dist/modes/interactive/components/daxnuts.js +140 -0
  274. package/dist/modes/interactive/components/diff.js +133 -0
  275. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  276. package/dist/modes/interactive/components/extension-editor.js +105 -0
  277. package/dist/modes/interactive/components/extension-input.js +61 -0
  278. package/dist/modes/interactive/components/extension-selector.js +78 -0
  279. package/dist/modes/interactive/components/footer.js +309 -0
  280. package/dist/modes/interactive/components/index.js +33 -0
  281. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  282. package/dist/modes/interactive/components/layout.js +64 -0
  283. package/dist/modes/interactive/components/login-dialog.js +148 -0
  284. package/dist/modes/interactive/components/model-selector.js +237 -0
  285. package/dist/modes/interactive/components/oauth-selector.js +111 -0
  286. package/dist/modes/interactive/components/session-selector-search.js +157 -0
  287. package/dist/modes/interactive/components/session-selector.js +860 -0
  288. package/dist/modes/interactive/components/settings-selector.js +123 -0
  289. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  290. package/dist/modes/interactive/components/skill-invocation-message.js +48 -0
  291. package/dist/modes/interactive/components/theme-selector.js +47 -0
  292. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  293. package/dist/modes/interactive/components/tool-execution.js +789 -0
  294. package/dist/modes/interactive/components/tool-group.js +106 -0
  295. package/dist/modes/interactive/components/tree-selector.js +962 -0
  296. package/dist/modes/interactive/components/user-message-selector.js +115 -0
  297. package/dist/modes/interactive/components/user-message.js +48 -0
  298. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  299. package/dist/modes/interactive/file-attachments.js +135 -0
  300. package/dist/modes/interactive/interactive-mode.js +3775 -0
  301. package/dist/modes/interactive/theme/dark.json +85 -0
  302. package/dist/modes/interactive/theme/light.json +85 -0
  303. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  304. package/dist/modes/interactive/theme/theme.js +177 -0
  305. package/dist/modes/print-mode.js +101 -0
  306. package/dist/modes/rpc/rpc-client.js +387 -0
  307. package/dist/modes/rpc/rpc-mode.js +509 -0
  308. package/dist/modes/rpc/rpc-types.js +8 -0
  309. package/dist/subagent-entry.js +145 -0
  310. package/dist/tool-names.js +34 -0
  311. package/dist/tui/autocomplete.js +596 -0
  312. package/dist/tui/components/box.js +104 -0
  313. package/dist/tui/components/cancellable-loader.js +35 -0
  314. package/dist/tui/components/editor.js +1679 -0
  315. package/dist/tui/components/image.js +69 -0
  316. package/dist/tui/components/input.js +433 -0
  317. package/dist/tui/components/loader.js +49 -0
  318. package/dist/tui/components/markdown.js +629 -0
  319. package/dist/tui/components/select-list.js +152 -0
  320. package/dist/tui/components/settings-list.js +185 -0
  321. package/dist/tui/components/spacer.js +23 -0
  322. package/dist/tui/components/text.js +89 -0
  323. package/dist/tui/components/truncated-text.js +51 -0
  324. package/dist/tui/editor-component.js +2 -0
  325. package/dist/tui/fuzzy.js +107 -0
  326. package/dist/tui/get-east-asian-width/index.js +32 -0
  327. package/dist/tui/get-east-asian-width/lookup.js +404 -0
  328. package/dist/tui/index.js +32 -0
  329. package/dist/tui/keybindings.js +114 -0
  330. package/dist/tui/keys.js +959 -0
  331. package/dist/tui/kill-ring.js +44 -0
  332. package/dist/tui/stdin-buffer.js +317 -0
  333. package/dist/tui/terminal-image.js +288 -0
  334. package/dist/tui/terminal.js +249 -0
  335. package/dist/tui/tui/autocomplete.js +596 -0
  336. package/dist/tui/tui/components/box.js +106 -0
  337. package/dist/tui/tui/components/cancellable-loader.js +35 -0
  338. package/dist/tui/tui/components/editor.js +1679 -0
  339. package/dist/tui/tui/components/image.js +69 -0
  340. package/dist/tui/tui/components/input.js +433 -0
  341. package/dist/tui/tui/components/loader.js +49 -0
  342. package/dist/tui/tui/components/markdown.js +629 -0
  343. package/dist/tui/tui/components/select-list.js +152 -0
  344. package/dist/tui/tui/components/settings-list.js +185 -0
  345. package/dist/tui/tui/components/spacer.js +23 -0
  346. package/dist/tui/tui/components/text.js +91 -0
  347. package/dist/tui/tui/components/truncated-text.js +51 -0
  348. package/dist/tui/tui/editor-component.js +2 -0
  349. package/dist/tui/tui/fuzzy.js +107 -0
  350. package/dist/tui/tui/get-east-asian-width/index.js +32 -0
  351. package/dist/tui/tui/get-east-asian-width/lookup.js +404 -0
  352. package/dist/tui/tui/index.js +32 -0
  353. package/dist/tui/tui/keybindings.js +114 -0
  354. package/dist/tui/tui/keys.js +959 -0
  355. package/dist/tui/tui/kill-ring.js +44 -0
  356. package/dist/tui/tui/stdin-buffer.js +317 -0
  357. package/dist/tui/tui/terminal-image.js +288 -0
  358. package/dist/tui/tui/terminal.js +249 -0
  359. package/dist/tui/tui/tui.js +955 -0
  360. package/dist/tui/tui/undo-stack.js +25 -0
  361. package/dist/tui/tui/utils.js +800 -0
  362. package/dist/tui/tui.js +955 -0
  363. package/dist/tui/undo-stack.js +25 -0
  364. package/dist/tui/utils.js +800 -0
  365. package/dist/utils/changelog.js +87 -0
  366. package/dist/utils/clipboard-image.js +164 -0
  367. package/dist/utils/clipboard-native.js +14 -0
  368. package/dist/utils/clipboard.js +67 -0
  369. package/dist/utils/frontmatter.js +26 -0
  370. package/dist/utils/git.js +166 -0
  371. package/dist/utils/image-convert.js +35 -0
  372. package/dist/utils/image-resize.js +183 -0
  373. package/dist/utils/mime.js +26 -0
  374. package/dist/utils/photon.js +121 -0
  375. package/dist/utils/shell.js +217 -0
  376. package/dist/utils/sleep.js +17 -0
  377. package/dist/utils/tools-manager.js +259 -0
  378. package/package.json +78 -0
  379. package/skills/excel-com-api/SKILL.md +74 -0
  380. package/skills/excel-com-api/excel-type-library.py +27767 -0
  381. package/skills/excel-com-api/office-type-library.py +10867 -0
  382. package/skills/integrations/SKILL.md +138 -0
  383. package/skills/integrations/alphasense.md +457 -0
  384. package/skills/integrations/bloomberg.md +803 -0
  385. package/skills/integrations/calcbench.md +315 -0
  386. package/skills/integrations/capiq.md +848 -0
  387. package/skills/integrations/dynamics-365-finance.md +354 -0
  388. package/skills/integrations/earnings_transcripts.md +387 -0
  389. package/skills/integrations/factset.md +758 -0
  390. package/skills/integrations/ice-fixed-income.md +344 -0
  391. package/skills/integrations/moodys-analytics.md +313 -0
  392. package/skills/integrations/morningstar.md +433 -0
  393. package/skills/integrations/nasdaq-data-link.md +249 -0
  394. package/skills/integrations/pitchbook.md +413 -0
  395. package/skills/integrations/preqin.md +422 -0
  396. package/skills/integrations/quickbooks.md +289 -0
  397. package/skills/integrations/quickfs.md +314 -0
  398. package/skills/integrations/refinitiv.md +473 -0
  399. package/skills/integrations/sage-intacct.md +401 -0
  400. package/skills/integrations/visible-alpha.md +320 -0
  401. package/skills/integrations/xero.md +393 -0
  402. package/skills/integrations/ycharts.md +306 -0
  403. package/skills/pdf-creation/SKILL.md +93 -0
  404. package/skills/pdf-extraction/SKILL.md +32 -0
  405. package/skills/powerpoint-creation/SKILL.md +110 -0
  406. package/skills/sec-edgar/SKILL.md +127 -0
  407. package/skills/sec-edgar/sec_to_pdf.py +109 -0
  408. package/xll/ShortcutXL.xll +0 -0
  409. package/xll/modules/debug_render.py +272 -0
  410. package/xll/modules/gameboy.py +241 -0
  411. package/xll/modules/pong.py +188 -0
  412. package/xll/modules/shortcut_xl/__init__.py +18 -0
  413. package/xll/modules/shortcut_xl/_categorize.py +200 -0
  414. package/xll/modules/shortcut_xl/_com.py +108 -0
  415. package/xll/modules/shortcut_xl/_format.py +252 -0
  416. package/xll/modules/shortcut_xl/_log.py +12 -0
  417. package/xll/modules/shortcut_xl/_managed.py +116 -0
  418. package/xll/modules/shortcut_xl/_registry.py +44 -0
  419. package/xll/modules/shortcut_xl/_threading.py +161 -0
  420. package/xll/modules/shortcut_xl/_tracking.py +283 -0
  421. package/xll/modules/stocks.py +100 -0
  422. package/xll/python3.dll +0 -0
  423. package/xll/python312.dll +0 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Shortcut OAuth Provider for the agent.
3
+ *
4
+ * Implements the pi-ai OAuthProviderInterface using Shortcut's Device Code Flow (RFC 8628).
5
+ * Users run `/login`, get a code, open browser to authorize, and the agent polls for tokens.
6
+ *
7
+ * Token refresh uses Shortcut's `POST /auth/refresh` endpoint with body mode (token_delivery: "body").
8
+ */
9
+ import { SHORTCUT_AUTH_URL as AUTH_SERVICE_URL } from '../constants.js';
10
+ const PLATFORM = 'agent-cli';
11
+ // Refresh tokens 2 minutes before expiry to avoid mid-request failures.
12
+ // The web client uses 5 min, but the agent only refreshes reactively (on getApiKey),
13
+ // so we use a shorter buffer — just enough to cover an in-flight streaming request.
14
+ const REFRESH_BUFFER_MS = 2 * 60 * 1000;
15
+ // PKCE utilities (inlined to avoid cross-package import issues)
16
+ const BASE64URL_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
17
+ function generateCodeVerifier() {
18
+ const array = new Uint8Array(64);
19
+ crypto.getRandomValues(array);
20
+ return Array.from(array, (byte) => BASE64URL_CHARSET[byte % 64]).join('');
21
+ }
22
+ async function generateCodeChallenge(codeVerifier) {
23
+ const data = new TextEncoder().encode(codeVerifier);
24
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
25
+ const hashArray = new Uint8Array(hashBuffer);
26
+ const base64 = btoa(String.fromCharCode(...hashArray));
27
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
28
+ }
29
+ /**
30
+ * Shortcut OAuth provider config (without `id` — pi-ai adds that from the provider name).
31
+ */
32
+ export const shortcutOAuth = {
33
+ name: 'Shortcut',
34
+ usesCallbackServer: false,
35
+ async login(callbacks) {
36
+ // 1. Initiate device code flow
37
+ const codeVerifier = generateCodeVerifier();
38
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
39
+ const codeResponse = await fetch(`${AUTH_SERVICE_URL}/auth/device/code`, {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({
43
+ code_challenge: codeChallenge,
44
+ platform: PLATFORM
45
+ }),
46
+ signal: callbacks.signal
47
+ });
48
+ if (!codeResponse.ok) {
49
+ const data = await codeResponse.json().catch(() => ({}));
50
+ throw new Error(data.error ?? `Failed to initiate device code: ${codeResponse.status}`);
51
+ }
52
+ const deviceCode = await codeResponse.json();
53
+ // 2. Tell the user to open the browser
54
+ callbacks.onAuth({
55
+ url: deviceCode.verification_uri_complete,
56
+ instructions: `Your code is: ${deviceCode.user_code}`
57
+ });
58
+ // 3. Poll for authorization
59
+ let interval = deviceCode.interval ?? 5;
60
+ const deadline = Date.now() + deviceCode.expires_in * 1000;
61
+ while (Date.now() < deadline) {
62
+ if (callbacks.signal?.aborted) {
63
+ throw new Error('Login cancelled');
64
+ }
65
+ await new Promise((resolve, reject) => {
66
+ const timeoutId = setTimeout(resolve, interval * 1000);
67
+ if (callbacks.signal) {
68
+ callbacks.signal.addEventListener('abort', () => {
69
+ clearTimeout(timeoutId);
70
+ reject(new Error('Login cancelled'));
71
+ }, { once: true });
72
+ }
73
+ });
74
+ let response;
75
+ try {
76
+ response = await fetch(`${AUTH_SERVICE_URL}/auth/device/token`, {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/json' },
79
+ body: JSON.stringify({
80
+ device_code: deviceCode.device_code,
81
+ code_verifier: codeVerifier,
82
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
83
+ }),
84
+ signal: callbacks.signal
85
+ });
86
+ }
87
+ catch {
88
+ // Network error — keep polling
89
+ continue;
90
+ }
91
+ if (response.ok) {
92
+ const tokens = await response.json();
93
+ return {
94
+ access: tokens.accessToken,
95
+ refresh: tokens.refreshToken,
96
+ expires: tokens.accessExpiresAt - REFRESH_BUFFER_MS
97
+ };
98
+ }
99
+ const errorData = await response.json().catch(() => ({ error: 'unknown' }));
100
+ const errorType = errorData.error;
101
+ if (errorType === 'authorization_pending') {
102
+ continue;
103
+ }
104
+ else if (errorType === 'slow_down') {
105
+ interval = errorData.interval ?? interval + 5;
106
+ continue;
107
+ }
108
+ else if (errorType === 'expired_token') {
109
+ throw new Error('Device code expired. Please try /login again.');
110
+ }
111
+ else if (errorType === 'access_denied') {
112
+ throw new Error('Authorization denied.');
113
+ }
114
+ else {
115
+ throw new Error(errorData.error_description ?? errorData.error ?? 'Login failed');
116
+ }
117
+ }
118
+ throw new Error('Device code expired. Please try /login again.');
119
+ },
120
+ async refreshToken(credentials) {
121
+ const response = await fetch(`${AUTH_SERVICE_URL}/auth/refresh`, {
122
+ method: 'POST',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify({
125
+ refreshToken: credentials.refresh,
126
+ token_delivery: 'body',
127
+ platform: PLATFORM
128
+ })
129
+ });
130
+ if (!response.ok) {
131
+ const data = await response.json().catch(() => ({}));
132
+ throw new Error(data.error ?? `Token refresh failed: ${response.status}`);
133
+ }
134
+ const tokens = await response.json();
135
+ return {
136
+ access: tokens.accessToken,
137
+ refresh: tokens.refreshToken,
138
+ expires: tokens.accessExpiresAt - REFRESH_BUFFER_MS
139
+ };
140
+ },
141
+ getApiKey(credentials) {
142
+ return credentials.access;
143
+ }
144
+ };
145
+ //# sourceMappingURL=shortcut-oauth.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shortcut production constants.
3
+ *
4
+ * These are the defaults when no .env file is present (i.e. installed product).
5
+ * For local development, create a .env file to override — see .env.development.
6
+ *
7
+ * Every env var listed here can be overridden via process.env.
8
+ */
9
+ // -- Shortcut infrastructure URLs ------------------------------------------
10
+ /** LLM proxy — routes LLM calls through the Python backend (auth, credits, model allowlist). */
11
+ export const SHORTCUT_LLM_PROXY_URL = process.env.SHORTCUT_LLM_PROXY_URL ?? 'https://agent.shortcut.ai';
12
+ /** Auth service — device code OAuth flow, token refresh. */
13
+ export const SHORTCUT_AUTH_URL = process.env.SHORTCUT_AUTH_URL ?? 'https://auth.shortcut.ai';
14
+ /** API service — credit balance, billing. */
15
+ export const SHORTCUT_API_URL = process.env.SHORTCUT_API_URL ?? 'https://api.shortcut.ai';
16
+ // -- Dev mode --------------------------------------------------------------
17
+ // DEV_MODE lives in config.ts (layer-0) so modes/ can import it without boundary violations.
18
+ // -- Excel XLL -------------------------------------------------------------
19
+ /** ShortcutXL HTTP server (always localhost — runs in-process with Excel). */
20
+ export const EXCEL_HTTP_URL = process.env.EXCEL_HTTP_URL ?? 'http://127.0.0.1:8080';
21
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Fetch a workbook summary from the running Excel instance via ShortcutXL.
3
+ *
4
+ * POSTs a small Python snippet to the XLL /exec endpoint that reads:
5
+ * - All open workbooks (not just the active one)
6
+ * - All sheet names with their used ranges per workbook
7
+ * - Which workbook and sheet are currently active
8
+ *
9
+ * Output format: first line is comma-separated workbook names (for easy parsing),
10
+ * followed by the full summary for the LLM.
11
+ */
12
+ import { EXCEL_HTTP_URL } from '../constants.js';
13
+ const SUMMARY_CODE = `
14
+ from shortcut_xl import xl_app
15
+ app = xl_app()
16
+ if app.Workbooks.Count == 0:
17
+ print("")
18
+ else:
19
+ active_wb = app.ActiveWorkbook
20
+ active_ws = app.ActiveSheet
21
+ active_wb_name = active_wb.Name if active_wb else None
22
+ active_ws_name = active_ws.Name if active_ws else None
23
+ names = [wb.Name for wb in app.Workbooks]
24
+ print(",".join(names))
25
+ for wb in app.Workbooks:
26
+ wb_marker = " (active)" if wb.Name == active_wb_name else ""
27
+ print(f"Workbook: {wb.Name}{wb_marker}")
28
+ print("Sheets and their used ranges:")
29
+ for ws in wb.Worksheets:
30
+ used = ws.UsedRange
31
+ addr = used.Address.replace("$", "")
32
+ ws_marker = " (active)" if wb.Name == active_wb_name and ws.Name == active_ws_name else ""
33
+ print(f" {ws.Name}: {addr}{ws_marker}")
34
+ `.trim();
35
+ /**
36
+ * Fetch the current workbook summary from Excel.
37
+ * Returns the raw summary string or null if unavailable.
38
+ * First line is comma-separated workbook names; rest is the full summary.
39
+ */
40
+ export async function fetchWorkbookSummary(httpUrl) {
41
+ const baseUrl = httpUrl ?? EXCEL_HTTP_URL;
42
+ try {
43
+ const response = await fetch(`${baseUrl}/exec`, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ code: SUMMARY_CODE }),
47
+ signal: AbortSignal.timeout(3000)
48
+ });
49
+ const result = (await response.json());
50
+ const output = result.ok ? result.output?.trim() : '';
51
+ return output || null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** Extract workbook names from the first line of a summary. */
58
+ export function parseWorkbookNames(summary) {
59
+ const firstLine = summary.split('\n')[0];
60
+ return firstLine
61
+ ? firstLine
62
+ .split(',')
63
+ .map((n) => n.trim())
64
+ .filter(Boolean)
65
+ : [];
66
+ }
67
+ /** Format summary as an LLM context block (strips the names header line). */
68
+ export function formatSummaryForLlm(summary) {
69
+ const lines = summary.split('\n');
70
+ const body = lines.slice(1).join('\n');
71
+ return `\n\n# Brief summary of the current open workbooks and sheets *(NOT an attachment)*\n\n${body}`;
72
+ }
73
+ //# sourceMappingURL=workbook-summary.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shortcut credit balance fetcher.
3
+ *
4
+ * Fetches the user's remaining credits from the Shortcut API
5
+ * and exposes them for display in the footer.
6
+ */
7
+ import { SHORTCUT_API_URL as API_BASE_URL } from '../constants.js';
8
+ /**
9
+ * Fetch the current credit balance for a Shortcut-authenticated user.
10
+ * Returns null on any failure (network, auth, etc.) — never throws.
11
+ */
12
+ export async function fetchCreditBalance(accessToken) {
13
+ try {
14
+ const response = await fetch(`${API_BASE_URL}/api/credits/balance`, {
15
+ headers: { Authorization: `Bearer ${accessToken}` }
16
+ });
17
+ if (!response.ok)
18
+ return null;
19
+ const data = await response.json();
20
+ return {
21
+ creditsRemaining: data.creditsRemaining ?? 0,
22
+ isUnlimited: data.isUnlimited ?? false
23
+ };
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ //# sourceMappingURL=shortcut-credits.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Cron daemon entry point.
3
+ *
4
+ * Called via `node dist/cli.js cron-daemon` (dispatched from main.ts).
5
+ * Minimal startup: auth refresh + daemon tick loop. No TUI, no tools.
6
+ */
7
+ import { AuthStorage } from '../../core/auth-storage.js';
8
+ import { startProactiveRefresh } from '../auth/refresh-timer.js';
9
+ import { SHORTCUT_PROVIDER_ID } from '../providers/provider-ids.js';
10
+ import { startDaemonLoop } from './daemon.js';
11
+ export async function runCronDaemon() {
12
+ // Keep OAuth tokens fresh for spawned agent processes
13
+ const authStorage = AuthStorage.create();
14
+ startProactiveRefresh(authStorage, SHORTCUT_PROVIDER_ID);
15
+ // Start the tick loop (runs forever until SIGTERM)
16
+ await startDaemonLoop();
17
+ }
18
+ //# sourceMappingURL=cron-daemon-entry.js.map
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Daemon IPC — start, stop, and query the cron daemon process.
3
+ *
4
+ * The daemon is a detached child process that outlives the parent.
5
+ * Communication is via PID file + signals (no sockets needed).
6
+ */
7
+ import { spawn } from 'node:child_process';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { resolveOwnBinary } from '../../config.js';
11
+ import { getCronDir } from './store.js';
12
+ export function getDaemonPidPath() {
13
+ return path.join(getCronDir(), 'daemon.pid');
14
+ }
15
+ function getDaemonLogPath() {
16
+ return path.join(getCronDir(), 'daemon.log');
17
+ }
18
+ /** Check if the daemon is alive by reading the PID file and pinging the process. */
19
+ export function getDaemonStatus() {
20
+ let pid;
21
+ try {
22
+ const raw = fs.readFileSync(getDaemonPidPath(), 'utf-8').trim();
23
+ pid = parseInt(raw, 10);
24
+ if (!Number.isFinite(pid) || pid <= 0)
25
+ return { running: false };
26
+ }
27
+ catch {
28
+ return { running: false };
29
+ }
30
+ try {
31
+ process.kill(pid, 0); // signal 0 = existence check
32
+ return { running: true, pid };
33
+ }
34
+ catch {
35
+ // Stale PID file — process is gone
36
+ cleanupPidFile();
37
+ return { running: false };
38
+ }
39
+ }
40
+ export function isDaemonRunning() {
41
+ return getDaemonStatus().running;
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // Start
45
+ // ---------------------------------------------------------------------------
46
+ /** Spawn the daemon as a detached background process. Returns the PID. */
47
+ export async function startDaemon() {
48
+ const status = getDaemonStatus();
49
+ if (status.running && status.pid) {
50
+ return { pid: status.pid, alreadyRunning: true };
51
+ }
52
+ await fs.promises.mkdir(getCronDir(), { recursive: true, mode: 0o700 });
53
+ const [cmd, ...args] = resolveOwnBinary();
54
+ // Wrap fd in try/finally to prevent leak on spawn error
55
+ const logFd = fs.openSync(getDaemonLogPath(), 'a');
56
+ let child;
57
+ try {
58
+ child = spawn(cmd, [...args, 'cron-daemon'], {
59
+ detached: true,
60
+ stdio: ['ignore', logFd, logFd],
61
+ cwd: process.env.HOME ?? process.cwd()
62
+ });
63
+ }
64
+ finally {
65
+ fs.closeSync(logFd);
66
+ }
67
+ // Guard against undefined pid (spawn failure)
68
+ if (!child.pid) {
69
+ throw new Error('Failed to spawn cron daemon process');
70
+ }
71
+ const pid = child.pid;
72
+ child.unref();
73
+ // PID file is written by the daemon itself (daemon.ts startDaemonLoop).
74
+ // Poll briefly for it to appear so callers get an accurate status.
75
+ for (let i = 0; i < 20; i++) {
76
+ await new Promise((r) => setTimeout(r, 50));
77
+ try {
78
+ fs.accessSync(getDaemonPidPath());
79
+ break;
80
+ }
81
+ catch {
82
+ // Not yet
83
+ }
84
+ }
85
+ return { pid, alreadyRunning: false };
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Stop
89
+ // ---------------------------------------------------------------------------
90
+ /** Stop the daemon by sending SIGTERM. Returns true if the daemon was running. */
91
+ export async function stopDaemon() {
92
+ const status = getDaemonStatus();
93
+ if (!status.running || !status.pid)
94
+ return false;
95
+ try {
96
+ process.kill(status.pid, 'SIGTERM');
97
+ }
98
+ catch {
99
+ // Process already gone
100
+ }
101
+ // Wait for process to exit before cleaning up PID file
102
+ const pid = status.pid;
103
+ let exited = false;
104
+ for (let i = 0; i < 20; i++) {
105
+ await new Promise((r) => setTimeout(r, 50));
106
+ try {
107
+ process.kill(pid, 0);
108
+ }
109
+ catch {
110
+ exited = true;
111
+ break;
112
+ }
113
+ }
114
+ if (exited) {
115
+ cleanupPidFile();
116
+ }
117
+ // If not exited after 1s, leave PID file so isDaemonRunning() still reports true
118
+ return true;
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Helpers
122
+ // ---------------------------------------------------------------------------
123
+ function cleanupPidFile() {
124
+ try {
125
+ fs.unlinkSync(getDaemonPidPath());
126
+ }
127
+ catch {
128
+ // Best effort
129
+ }
130
+ }
131
+ //# sourceMappingURL=daemon-ipc.js.map
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Cron daemon — background timer loop that executes due jobs.
3
+ *
4
+ * Runs as a detached Node process (spawned via daemon-ipc.ts).
5
+ * On each tick: load store → find due jobs → execute sequentially → save.
6
+ */
7
+ import { spawn as nodeSpawn } from 'node:child_process';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { resolveOwnBinary } from '../../config.js';
11
+ import { getDaemonPidPath } from './daemon-ipc.js';
12
+ import { applyJobResult, isJobDue, normalizeJobState } from './jobs.js';
13
+ import { appendRunLog, truncateRunOutput } from './run-log.js';
14
+ import { clearStoreCache, getCronStorePath, loadCronStore, saveCronStore } from './store.js';
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ /** Tick interval — poll this often. */
19
+ const TICK_INTERVAL_MS = 60_000;
20
+ /** Timeout for a single job execution. */
21
+ const JOB_TIMEOUT_MS = 10 * 60_000; // 10 minutes
22
+ /** Max captured output per stream (stdout/stderr) to prevent OOM. */
23
+ const MAX_OUTPUT_BYTES = 1024 * 1024; // 1 MB
24
+ // ---------------------------------------------------------------------------
25
+ // State
26
+ // ---------------------------------------------------------------------------
27
+ let tickTimer = null;
28
+ let stopping = false;
29
+ let activeChild = null;
30
+ // ---------------------------------------------------------------------------
31
+ // Job execution
32
+ // ---------------------------------------------------------------------------
33
+ /** Spawn the agent binary in print mode for a job's payload. */
34
+ function spawnAgentForJob(job) {
35
+ return new Promise((resolve) => {
36
+ const [cmd, ...baseArgs] = resolveOwnBinary();
37
+ const args = [
38
+ ...baseArgs,
39
+ '-p',
40
+ '--no-session',
41
+ ...(job.payload.model ? ['--model', job.payload.model] : []),
42
+ job.payload.message
43
+ ];
44
+ let stdout = '';
45
+ let stderr = '';
46
+ let settled = false;
47
+ const child = nodeSpawn(cmd, args, {
48
+ stdio: ['ignore', 'pipe', 'pipe'],
49
+ cwd: process.env.HOME ?? process.cwd()
50
+ });
51
+ activeChild = child;
52
+ child.stdout?.on('data', (chunk) => {
53
+ if (stdout.length < MAX_OUTPUT_BYTES)
54
+ stdout += chunk.toString();
55
+ });
56
+ child.stderr?.on('data', (chunk) => {
57
+ if (stderr.length < MAX_OUTPUT_BYTES)
58
+ stderr += chunk.toString();
59
+ });
60
+ let escalation = null;
61
+ const timeout = setTimeout(() => {
62
+ child.kill('SIGTERM');
63
+ stderr += '\n[daemon] Job timed out';
64
+ // Escalate to SIGKILL if process doesn't exit within 5s
65
+ escalation = setTimeout(() => child.kill('SIGKILL'), 5_000);
66
+ escalation.unref();
67
+ }, JOB_TIMEOUT_MS);
68
+ child.on('close', (code) => {
69
+ if (settled)
70
+ return;
71
+ settled = true;
72
+ activeChild = null;
73
+ clearTimeout(timeout);
74
+ if (escalation)
75
+ clearTimeout(escalation);
76
+ resolve({
77
+ exitCode: code ?? 1,
78
+ output: stdout.trim(),
79
+ error: stderr.trim()
80
+ });
81
+ });
82
+ child.on('error', (err) => {
83
+ if (settled)
84
+ return;
85
+ settled = true;
86
+ activeChild = null;
87
+ clearTimeout(timeout);
88
+ if (escalation)
89
+ clearTimeout(escalation);
90
+ resolve({
91
+ exitCode: 1,
92
+ output: '',
93
+ error: err.message
94
+ });
95
+ });
96
+ });
97
+ }
98
+ async function executeJob(job, storePath) {
99
+ const startedAt = Date.now();
100
+ log(`Executing job "${job.name}" (${job.id})`);
101
+ const { exitCode, output, error } = await spawnAgentForJob(job);
102
+ const endedAt = Date.now();
103
+ const result = {
104
+ status: exitCode === 0 ? 'ok' : 'error',
105
+ error: exitCode !== 0 ? error || `exit code ${exitCode}` : undefined,
106
+ startedAt,
107
+ endedAt
108
+ };
109
+ const shouldDelete = applyJobResult(job, result);
110
+ // Reload store before saving to pick up any concurrent changes
111
+ clearStoreCache();
112
+ const store = await loadCronStore(storePath);
113
+ const storeJob = store.jobs.find((j) => j.id === job.id);
114
+ if (storeJob) {
115
+ // Selectively merge only fields that applyJobResult modifies,
116
+ // preserving any concurrent user changes to other state fields
117
+ storeJob.state.runningAtMs = job.state.runningAtMs;
118
+ storeJob.state.lastRunAtMs = job.state.lastRunAtMs;
119
+ storeJob.state.lastRunStatus = job.state.lastRunStatus;
120
+ storeJob.state.lastDurationMs = job.state.lastDurationMs;
121
+ storeJob.state.lastError = job.state.lastError;
122
+ storeJob.state.consecutiveErrors = job.state.consecutiveErrors;
123
+ storeJob.state.nextRunAtMs = job.state.nextRunAtMs;
124
+ storeJob.enabled = job.enabled;
125
+ storeJob.updatedAtMs = job.updatedAtMs;
126
+ if (shouldDelete) {
127
+ store.jobs = store.jobs.filter((j) => j.id !== job.id);
128
+ }
129
+ }
130
+ await saveCronStore(storePath, store);
131
+ await appendRunLog({
132
+ jobId: job.id,
133
+ jobName: job.name,
134
+ startedAtMs: startedAt,
135
+ endedAtMs: endedAt,
136
+ durationMs: endedAt - startedAt,
137
+ status: result.status,
138
+ error: result.error,
139
+ output: output ? truncateRunOutput(output) : undefined
140
+ });
141
+ const statusMsg = result.status === 'ok' ? `completed in ${endedAt - startedAt}ms` : `failed: ${result.error}`;
142
+ log(`Job "${job.name}" ${statusMsg}`);
143
+ }
144
+ // ---------------------------------------------------------------------------
145
+ // Tick loop
146
+ // ---------------------------------------------------------------------------
147
+ async function tick(storePath) {
148
+ if (stopping)
149
+ return;
150
+ try {
151
+ clearStoreCache();
152
+ const store = await loadCronStore(storePath);
153
+ const now = Date.now();
154
+ // Normalize all jobs (clear stuck runs, repair missing schedules)
155
+ let storeChanged = false;
156
+ for (const job of store.jobs) {
157
+ if (normalizeJobState(job, now))
158
+ storeChanged = true;
159
+ }
160
+ if (storeChanged) {
161
+ await saveCronStore(storePath, store);
162
+ }
163
+ // Find and execute due jobs sequentially
164
+ const dueJobs = store.jobs.filter((j) => isJobDue(j, now));
165
+ for (const job of dueJobs) {
166
+ if (stopping)
167
+ break;
168
+ // Mark as running
169
+ job.state.runningAtMs = Date.now();
170
+ await saveCronStore(storePath, store);
171
+ await executeJob(job, storePath);
172
+ }
173
+ }
174
+ catch (err) {
175
+ log(`Tick error: ${err instanceof Error ? err.message : String(err)}`);
176
+ }
177
+ armTimer(storePath);
178
+ }
179
+ function armTimer(storePath) {
180
+ if (stopping)
181
+ return;
182
+ if (tickTimer)
183
+ clearTimeout(tickTimer);
184
+ tickTimer = setTimeout(() => void tick(storePath), TICK_INTERVAL_MS);
185
+ tickTimer.unref();
186
+ }
187
+ // ---------------------------------------------------------------------------
188
+ // Lifecycle
189
+ // ---------------------------------------------------------------------------
190
+ function log(msg) {
191
+ const ts = new Date().toISOString();
192
+ process.stdout.write(`[${ts}] ${msg}\n`);
193
+ }
194
+ /** Start the daemon tick loop. Call from the daemon entry point. */
195
+ export async function startDaemonLoop(storePath = getCronStorePath()) {
196
+ log('Cron daemon starting');
197
+ // Write PID file
198
+ await fs.promises.mkdir(path.dirname(getDaemonPidPath()), { recursive: true, mode: 0o700 });
199
+ await fs.promises.writeFile(getDaemonPidPath(), String(process.pid), { mode: 0o600 });
200
+ // Handle shutdown signals
201
+ const shutdown = () => {
202
+ log('Cron daemon shutting down');
203
+ stopping = true;
204
+ if (tickTimer) {
205
+ clearTimeout(tickTimer);
206
+ tickTimer = null;
207
+ }
208
+ if (activeChild) {
209
+ activeChild.kill('SIGTERM');
210
+ }
211
+ try {
212
+ fs.unlinkSync(getDaemonPidPath());
213
+ }
214
+ catch {
215
+ // best effort
216
+ }
217
+ process.exit(0);
218
+ };
219
+ process.on('SIGTERM', shutdown);
220
+ process.on('SIGINT', shutdown);
221
+ // Run first tick immediately
222
+ await tick(storePath);
223
+ }
224
+ //# sourceMappingURL=daemon.js.map