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,188 @@
1
+ """
2
+ Pong in Excel — type =pong_start() to play!
3
+
4
+ Two AI paddles rally a ball across a 30x20 cell grid.
5
+ Type =pong_stop() to end the game.
6
+ """
7
+ import shortcut_xl
8
+ from shortcut_xl import xl_func
9
+ import random
10
+
11
+ WIDTH = 30
12
+ HEIGHT = 20
13
+ PADDLE_H = 4
14
+ FPS = 30
15
+
16
+ # Grid occupies A3:AD22 (20 rows x 30 cols)
17
+ _GRID_RANGE = "A3:AD22"
18
+
19
+ _state = {
20
+ 'ball_x': 15.0, 'ball_y': 10.0,
21
+ 'ball_dx': 1.0, 'ball_dy': 0.7,
22
+ 'paddle_l': 8, 'paddle_r': 8,
23
+ 'score_l': 0, 'score_r': 0,
24
+ 'running': False,
25
+ }
26
+
27
+
28
+ def _reset_ball():
29
+ _state['ball_x'] = WIDTH / 2.0
30
+ _state['ball_y'] = HEIGHT / 2.0
31
+ _state['ball_dx'] = random.choice([-1.0, 1.0])
32
+ _state['ball_dy'] = random.uniform(-1.0, 1.0)
33
+
34
+
35
+ def _render():
36
+ # Build the grid
37
+ grid = []
38
+ for r in range(HEIGHT):
39
+ row = []
40
+ for c in range(WIDTH):
41
+ ch = ""
42
+ # Left paddle
43
+ if c == 0 and _state['paddle_l'] <= r < _state['paddle_l'] + PADDLE_H:
44
+ ch = chr(9608) # full block
45
+ # Right paddle
46
+ elif c == WIDTH - 1 and _state['paddle_r'] <= r < _state['paddle_r'] + PADDLE_H:
47
+ ch = chr(9608)
48
+ # Ball
49
+ elif int(round(_state['ball_y'])) == r and int(round(_state['ball_x'])) == c:
50
+ ch = chr(9679) # black circle
51
+ # Center line
52
+ elif c == WIDTH // 2 and r % 2 == 0:
53
+ ch = chr(9474) # light vertical
54
+ row.append(ch)
55
+ grid.append(row)
56
+
57
+ # Write entire frame in one batch — one repaint, one retry point
58
+ score = f"PONG {_state['score_l']} : {_state['score_r']}"
59
+
60
+ def _paint(app):
61
+ # Suppress auto-recalculation — without this every 600-cell write
62
+ # triggers a full recalc cycle that fights our rendering and causes
63
+ # the visible "flashing" effect.
64
+ app.Calculation = -4135 # xlCalculationManual
65
+
66
+ sheet = app.ActiveSheet
67
+
68
+ # One-time setup: make cells square-ish and use a monospace font
69
+ # so the Unicode glyphs (█ ● │) are actually visible.
70
+ if not _state.get('_formatted'):
71
+ _state['_formatted'] = True
72
+ area = sheet.Range(_GRID_RANGE)
73
+ area.ColumnWidth = 2.14
74
+ area.Font.Name = "Consolas"
75
+ area.Font.Size = 11
76
+ area.HorizontalAlignment = -4108 # xlCenter
77
+
78
+ # Grid (20x30) — use direct range address, NOT Resize()
79
+ # win32com Resize() is broken: returns only the bottom-right cell
80
+ grid_com = tuple(tuple(row) for row in grid)
81
+ sheet.Range(_GRID_RANGE).Value = grid_com
82
+ # Scoreboard + instructions
83
+ sheet.Range("A1").Value = score
84
+ sheet.Range("A2").Value = "pong_start() to play pong_stop() to end"
85
+
86
+ shortcut_xl.xl_batch(_paint)
87
+
88
+
89
+ def _tick():
90
+ shortcut_xl.xl_log(f"_tick: running={_state['running']}")
91
+ if not _state['running']:
92
+ return
93
+
94
+ try:
95
+ _tick_inner()
96
+ shortcut_xl.xl_log("_tick: frame OK")
97
+ except Exception as e:
98
+ shortcut_xl.xl_log(f"pong tick error: {e}")
99
+ import traceback
100
+ shortcut_xl.xl_log(traceback.format_exc())
101
+ _state['running'] = False
102
+ shortcut_xl.schedule_call(_restore_calc, 0.3)
103
+ return
104
+
105
+
106
+ def _tick_inner():
107
+ s = _state
108
+
109
+ # Move ball
110
+ s['ball_x'] += s['ball_dx']
111
+ s['ball_y'] += s['ball_dy']
112
+
113
+ # Bounce top/bottom
114
+ if s['ball_y'] <= 0:
115
+ s['ball_y'] = 0
116
+ s['ball_dy'] = abs(s['ball_dy'])
117
+ elif s['ball_y'] >= HEIGHT - 1:
118
+ s['ball_y'] = HEIGHT - 1
119
+ s['ball_dy'] = -abs(s['ball_dy'])
120
+
121
+ # Left paddle check
122
+ if s['ball_x'] <= 1:
123
+ if s['paddle_l'] - 0.5 <= s['ball_y'] < s['paddle_l'] + PADDLE_H + 0.5:
124
+ s['ball_dx'] = abs(s['ball_dx'])
125
+ s['ball_x'] = 1
126
+ # Add spin based on where ball hit paddle
127
+ hit_pos = (s['ball_y'] - s['paddle_l']) / PADDLE_H
128
+ s['ball_dy'] = (hit_pos - 0.5) * 2.0
129
+ else:
130
+ s['score_r'] += 1
131
+ _reset_ball()
132
+
133
+ # Right paddle check
134
+ if s['ball_x'] >= WIDTH - 2:
135
+ if s['paddle_r'] - 0.5 <= s['ball_y'] < s['paddle_r'] + PADDLE_H + 0.5:
136
+ s['ball_dx'] = -abs(s['ball_dx'])
137
+ s['ball_x'] = WIDTH - 2
138
+ hit_pos = (s['ball_y'] - s['paddle_r']) / PADDLE_H
139
+ s['ball_dy'] = (hit_pos - 0.5) * 2.0
140
+ else:
141
+ s['score_l'] += 1
142
+ _reset_ball()
143
+
144
+ # AI: move paddles toward ball with slight delay
145
+ ball_y = s['ball_y']
146
+ for paddle in ('paddle_l', 'paddle_r'):
147
+ target = int(ball_y) - PADDLE_H // 2
148
+ diff = target - s[paddle]
149
+ # Add some imperfection
150
+ speed = 1 if abs(diff) < 3 else 2
151
+ if diff > 0:
152
+ s[paddle] = min(s[paddle] + speed, HEIGHT - PADDLE_H)
153
+ elif diff < 0:
154
+ s[paddle] = max(s[paddle] - speed, 0)
155
+
156
+ # Speed up slightly over time
157
+ if abs(s['ball_dx']) < 2.5:
158
+ s['ball_dx'] *= 1.005
159
+
160
+ _render()
161
+ shortcut_xl.schedule_call(_tick, 1.0 / FPS)
162
+
163
+
164
+ def _restore_calc():
165
+ """Restore automatic calculation after the game ends."""
166
+ def _do(app):
167
+ app.Calculation = -4105 # xlCalculationAutomatic
168
+ shortcut_xl.xl_batch(_do)
169
+
170
+
171
+ @xl_func
172
+ def pong_start():
173
+ if _state['running']:
174
+ return "Already running!"
175
+ _state['running'] = True
176
+ _state['score_l'] = 0
177
+ _state['score_r'] = 0
178
+ _state['_formatted'] = False
179
+ _reset_ball()
180
+ shortcut_xl.schedule_call(_tick, 0.1)
181
+ return "PONG!"
182
+
183
+
184
+ @xl_func
185
+ def pong_stop():
186
+ _state['running'] = False
187
+ shortcut_xl.schedule_call(_restore_calc, 0.3)
188
+ return "Stopped"
@@ -0,0 +1,18 @@
1
+ """ShortcutXL — Python helpers for the Excel XLL add-in.
2
+
3
+ Public API
4
+ ----------
5
+ @xl_func — decorator to register a function as an Excel UDF
6
+ xl_app() — return the Excel Application COM object
7
+ run_managed(fn) — run fn(app) on main thread with tracking + state management
8
+ xl_batch(fn) — run fn(app) on the main thread with ScreenUpdating off
9
+ schedule_call(fn) — schedule fn() on a background thread
10
+ xl_log(msg) — append to %TEMP%\\shortcutxl.log
11
+ format_cell_diff — format dirty cells into a human-readable summary
12
+ """
13
+
14
+ from ._log import xl_log
15
+ from ._registry import xl_func, _registry
16
+ from ._com import xl_app
17
+ from ._managed import run_managed, xl_batch, schedule_call
18
+ from ._format import format_cell_diff
@@ -0,0 +1,200 @@
1
+ """Cell issue categorization — port of cell-categorizer.ts.
2
+
3
+ Detects hardcoded numbers, formula errors, large percentages,
4
+ and hardcoded numbers inside formulas.
5
+ """
6
+
7
+ import re
8
+
9
+ # Issue types — also used as dict keys in categorize_cells() result
10
+ GOOD = 'good'
11
+ HARDCODED_NUMBER = 'hardcodes'
12
+ HARDCODED_NUMBER_IN_FORMULA = 'hardcodes in formulas'
13
+ LARGE_PERCENTAGE = 'large %'
14
+ INVALID_FORMULA = 'formula errs'
15
+ ALL_PROBLEMS = 'all problems'
16
+
17
+ # Number format characters that indicate date/time (skip these as hardcoded)
18
+ _DATE_TIME_CHARS = ('d', 'm', 'y', 'h', 's')
19
+
20
+ # Number format characters/strings that indicate currency/accounting (skip these)
21
+ # Must stay in sync with _format.py currency regex.
22
+ _CURRENCY_CHARS = ('$', '£', '€', '¥', '₹', '₩', '₪', '₱', '฿', 'kr')
23
+
24
+
25
+ def _is_numeric(value):
26
+ """Check if a value is numeric (int, float, Decimal, etc.)."""
27
+ if isinstance(value, bool):
28
+ return False
29
+ if isinstance(value, (int, float)):
30
+ return True
31
+ # COM may return Decimal or other numeric types
32
+ try:
33
+ float(value)
34
+ return True
35
+ except (TypeError, ValueError):
36
+ return False
37
+
38
+
39
+ def _to_float(value):
40
+ """Coerce a numeric value to float."""
41
+ if isinstance(value, (int, float)):
42
+ return value
43
+ return float(value)
44
+
45
+
46
+ def is_hardcoded_number(cell):
47
+ """Check if a cell contains a hardcoded number (no formula, numeric value,
48
+ not a date/time/currency format, not a COM error code)."""
49
+ if cell.get('formula'):
50
+ return False
51
+ value = cell.get('value')
52
+ if not _is_numeric(value):
53
+ return False
54
+ if is_com_error(value):
55
+ return False
56
+ fmt = (cell.get('numberFormat') or '').lower()
57
+ # Skip dates/times
58
+ if any(ch in fmt for ch in _DATE_TIME_CHARS):
59
+ return False
60
+ # Skip currency/accounting
61
+ if any(ch in fmt for ch in _CURRENCY_CHARS):
62
+ return False
63
+ return True
64
+
65
+
66
+ # Matches standalone numbers in formulas, excluding cell refs / named ranges.
67
+ _HARDCODED_NUM_RE = re.compile(
68
+ r'(?<![A-Z$!])(?<![A-Z]\d)\b\d+\.?\d*\b(?![A-Z])', re.IGNORECASE)
69
+
70
+
71
+ def has_hardcoded_numbers_in_formula(formula):
72
+ """Check if a formula contains hardcoded numbers."""
73
+ # Remove string literals to avoid false positives
74
+ cleaned = re.sub(r'"[^"]*"', '', formula)
75
+ return bool(_HARDCODED_NUM_RE.search(cleaned))
76
+
77
+
78
+ _ERROR_PATTERNS = (
79
+ '#ERROR', '#REF', '#VALUE', '#DIV/0', '#NUM', '#N/A', '#NAME', '#NULL')
80
+
81
+ # COM returns Excel error values as negative integers (CVErr codes).
82
+ # Map them to their string equivalents so categorization works.
83
+ _COM_ERROR_CODES = {
84
+ -2146826281: '#DIV/0!',
85
+ -2146826246: '#N/A',
86
+ -2146826259: '#NAME?',
87
+ -2146826265: '#REF!',
88
+ -2146826273: '#VALUE!',
89
+ -2146826252: '#NUM!',
90
+ -2146826288: '#NULL!',
91
+ -2146826245: '#GETTING_DATA',
92
+ }
93
+
94
+
95
+ def _is_error_value(s):
96
+ """Check if a string is an Excel error value (e.g. '#REF!', '#N/A').
97
+
98
+ Excel error values always start with '#' and may have a trailing '!'
99
+ or '?'. We check that the string starts with a known error prefix
100
+ and contains no spaces (real errors never do)."""
101
+ if not s or ' ' in s:
102
+ return False
103
+ return any(s.startswith(e) for e in _ERROR_PATTERNS)
104
+
105
+
106
+ def is_com_error(value):
107
+ """Check if a value is a COM error code (negative integer)."""
108
+ return isinstance(value, int) and value in _COM_ERROR_CODES
109
+
110
+
111
+ def com_error_to_str(value):
112
+ """Convert a COM error code to its Excel error string, or None."""
113
+ return _COM_ERROR_CODES.get(value)
114
+
115
+
116
+ def has_formula_error(cell):
117
+ """Check if a cell has a formula error.
118
+
119
+ Handles both string error values (#DIV/0!, #REF!, etc.) and
120
+ COM integer error codes (-2146826281, etc.) returned by win32com.
121
+ """
122
+ value = cell.get('value')
123
+ if is_com_error(value):
124
+ return True
125
+ value_str = str(value or '')
126
+ formula_str = str(cell.get('formula') or '')
127
+ return _is_error_value(value_str) or _is_error_value(formula_str)
128
+
129
+
130
+ def is_large_percentage(cell):
131
+ """Check if a cell contains a large percentage (>= 100%, hardcoded)."""
132
+ fmt = cell.get('numberFormat') or ''
133
+ if '%' not in fmt:
134
+ return False
135
+ if cell.get('formula'):
136
+ return False
137
+ value = cell.get('value')
138
+ if not _is_numeric(value):
139
+ return False
140
+ # In spreadsheets, 1.0 = 100%
141
+ return abs(_to_float(value)) >= 1
142
+
143
+
144
+ def categorize_cell(cell):
145
+ """Return (cell, issues_list) for a single cell dict."""
146
+ issues = []
147
+ if is_large_percentage(cell):
148
+ issues.append(LARGE_PERCENTAGE)
149
+ if is_hardcoded_number(cell):
150
+ issues.append(HARDCODED_NUMBER)
151
+ is_error = has_formula_error(cell)
152
+ if is_error:
153
+ issues.append(INVALID_FORMULA)
154
+ # Only flag hardcoded numbers in formula when there's no error —
155
+ # error formulas need the error fixed first, flagging "=1/0" for
156
+ # containing hardcoded 1 and 0 is just noise.
157
+ if not is_error and cell.get('formula') and has_hardcoded_numbers_in_formula(cell['formula']):
158
+ issues.append(HARDCODED_NUMBER_IN_FORMULA)
159
+ return issues
160
+
161
+
162
+ def categorize_cells(dirty_cells):
163
+ """Categorize a list of dirty cell dicts into groups.
164
+
165
+ Returns a dict with keys:
166
+ good, large_percentages, hardcoded_numbers,
167
+ hardcoded_in_formulas, invalid_formulas, all_problems
168
+ Each value is a list of (cell, issues) tuples (except good which is just cells).
169
+ """
170
+ good = []
171
+ large_pct = []
172
+ hardcoded = []
173
+ hardcoded_in_formula = []
174
+ invalid = []
175
+ all_problems = []
176
+
177
+ for cell in dirty_cells:
178
+ issues = categorize_cell(cell)
179
+ if not issues:
180
+ good.append(cell)
181
+ else:
182
+ entry = (cell, issues)
183
+ all_problems.append(entry)
184
+ if LARGE_PERCENTAGE in issues:
185
+ large_pct.append(entry)
186
+ if HARDCODED_NUMBER in issues:
187
+ hardcoded.append(entry)
188
+ if HARDCODED_NUMBER_IN_FORMULA in issues:
189
+ hardcoded_in_formula.append(entry)
190
+ if INVALID_FORMULA in issues:
191
+ invalid.append(entry)
192
+
193
+ return {
194
+ GOOD: good,
195
+ LARGE_PERCENTAGE: large_pct,
196
+ HARDCODED_NUMBER: hardcoded,
197
+ HARDCODED_NUMBER_IN_FORMULA: hardcoded_in_formula,
198
+ INVALID_FORMULA: invalid,
199
+ ALL_PROBLEMS: all_problems,
200
+ }
@@ -0,0 +1,108 @@
1
+ """Excel COM helpers — locate the Excel Application object."""
2
+
3
+ import ctypes
4
+ from ctypes import wintypes
5
+ from ._log import xl_log
6
+
7
+ # AccessibleObjectFromWindow native object ID
8
+ _OBJID_NATIVEOM = -16
9
+
10
+ # Excel window class names (hierarchy: XLMAIN → XLDESK → EXCEL7)
11
+ _WC_XLMAIN = "XLMAIN"
12
+ _WC_XLDESK = "XLDESK"
13
+ _WC_EXCEL7 = "EXCEL7"
14
+
15
+
16
+ class _GUID(ctypes.Structure):
17
+ _fields_ = [
18
+ ('Data1', ctypes.c_ulong),
19
+ ('Data2', ctypes.c_ushort),
20
+ ('Data3', ctypes.c_ushort),
21
+ ('Data4', ctypes.c_byte * 8),
22
+ ]
23
+
24
+
25
+ # IID_IDispatch = {00020400-0000-0000-C000-000000000046}
26
+ _IID_IDISPATCH = _GUID(
27
+ 0x00020400, 0x0000, 0x0000,
28
+ (ctypes.c_byte * 8)(0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46))
29
+
30
+
31
+ _user32 = ctypes.windll.user32
32
+
33
+
34
+ def _find_own_xlmain():
35
+ """Find the XLMAIN window belonging to our own process.
36
+
37
+ The XLL runs in-process, so our PID == Excel's PID.
38
+ EnumWindows + GetWindowThreadProcessId ensures we find the right
39
+ instance even when multiple Excels are open.
40
+
41
+ Uses local variables captured by a closure instead of module-level
42
+ mutable globals, making this safe for concurrent calls."""
43
+ target_pid = ctypes.windll.kernel32.GetCurrentProcessId()
44
+ result = ctypes.c_void_p(0)
45
+
46
+ @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
47
+ def callback(hwnd, _lparam):
48
+ buf = ctypes.create_unicode_buffer(32)
49
+ _user32.GetClassNameW(hwnd, buf, 32)
50
+ if buf.value != _WC_XLMAIN:
51
+ return True
52
+ pid = wintypes.DWORD()
53
+ _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
54
+ if pid.value == target_pid:
55
+ result.value = hwnd
56
+ return False
57
+ return True
58
+
59
+ _user32.EnumWindows(callback, 0)
60
+ return result.value
61
+
62
+
63
+ def _ensure_com_init():
64
+ """Initialize COM on the current thread (idempotent)."""
65
+ import pythoncom
66
+ pythoncom.CoInitialize()
67
+
68
+
69
+ def xl_app():
70
+ """Return the Excel Application COM object.
71
+
72
+ Performs a fresh HWND lookup every call. The cost is negligible
73
+ (microseconds) and avoids stale-reference bugs from caching.
74
+
75
+ Uses EnumWindows + PID matching to find this process's Excel,
76
+ not just the first XLMAIN window on the desktop."""
77
+ _ensure_com_init()
78
+ import pythoncom
79
+ import win32com.client
80
+
81
+ # Use windll (not oledll) so we get the HRESULT as a return value
82
+ # instead of an auto-raised OSError — lets us fall through to the
83
+ # GetActiveObject fallback on failure.
84
+ oleacc = ctypes.windll.oleacc
85
+
86
+ xlmain = _find_own_xlmain()
87
+ if xlmain:
88
+ xldesk = _user32.FindWindowExW(xlmain, 0, _WC_XLDESK, None)
89
+ excel7 = _user32.FindWindowExW(xldesk, 0, _WC_EXCEL7, None) if xldesk else 0
90
+ if excel7:
91
+ punk = ctypes.c_void_p()
92
+ hr = oleacc.AccessibleObjectFromWindow(
93
+ excel7, _OBJID_NATIVEOM, ctypes.byref(_IID_IDISPATCH), ctypes.byref(punk))
94
+ if hr == 0 and punk.value:
95
+ iunk = pythoncom.ObjectFromAddress(punk.value)
96
+ disp = iunk.QueryInterface(pythoncom.IID_IDispatch)
97
+ window = win32com.client.Dispatch(disp)
98
+ return window.Application
99
+
100
+ # Fallback — try ROT, but NEVER create a new instance.
101
+ xl_log("xl_app: HWND approach failed, trying GetActiveObject")
102
+ try:
103
+ return win32com.client.GetActiveObject("Excel.Application")
104
+ except Exception as e:
105
+ raise RuntimeError(
106
+ "Could not find a running Excel instance. "
107
+ "Make sure Excel is open before loading the XLL."
108
+ ) from e