shortcutxl 0.3.76 → 0.3.78

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 (480) hide show
  1. package/BINARY-INVENTORY.json +813 -813
  2. package/CHANGELOG.md +14 -0
  3. package/dist/ai/models.d.ts +10 -6
  4. package/dist/ai/models.generated.d.ts +72 -0
  5. package/dist/ai/models.generated.js +33 -0
  6. package/dist/ai/models.js +36 -13
  7. package/dist/ai/providers/anthropic.d.ts +4 -2
  8. package/dist/ai/providers/anthropic.js +59 -29
  9. package/dist/ai/providers/azure-openai-responses.d.ts +2 -1
  10. package/dist/ai/providers/azure-openai-responses.js +4 -20
  11. package/dist/ai/providers/google-vertex.d.ts +8 -2
  12. package/dist/ai/providers/google-vertex.js +66 -54
  13. package/dist/ai/providers/google.js +15 -48
  14. package/dist/ai/providers/openai-completions.d.ts +4 -1
  15. package/dist/ai/providers/openai-completions.js +26 -10
  16. package/dist/ai/providers/openai-reasoning.d.ts +5 -0
  17. package/dist/ai/providers/openai-reasoning.js +13 -0
  18. package/dist/ai/providers/openai-responses-shared.js +1 -1
  19. package/dist/ai/providers/openai-responses.d.ts +6 -2
  20. package/dist/ai/providers/openai-responses.js +28 -23
  21. package/dist/ai/providers/simple-options.d.ts +2 -3
  22. package/dist/ai/providers/simple-options.js +4 -6
  23. package/dist/ai/types.d.ts +19 -2
  24. package/dist/app/agent-session-runtime-adapter.js +7 -6
  25. package/dist/app/agent-session.d.ts +0 -15
  26. package/dist/app/agent-session.js +0 -37
  27. package/dist/app/approvals/approval-errors.d.ts +15 -0
  28. package/dist/app/approvals/approval-errors.js +14 -0
  29. package/dist/app/approvals/runtime-approval-gates.d.ts +1 -2
  30. package/dist/app/approvals/tool-approval.d.ts +1 -0
  31. package/dist/app/approvals/tool-approval.js +11 -23
  32. package/dist/app/approvals/types.d.ts +0 -14
  33. package/dist/app/approvals/types.js +0 -2
  34. package/dist/app/background/tool-summaries.js +0 -2
  35. package/dist/app/extensions/slash-commands.js +1 -3
  36. package/dist/app/local/tools/execute-code/executor.d.ts +1 -1
  37. package/dist/app/local/tools/execute-code/executor.js +20 -10
  38. package/dist/app/local/tools/execute-code/tool.js +21 -2
  39. package/dist/app/modes/action/agent.js +4 -5
  40. package/dist/app/modes/ask/agent.js +4 -6
  41. package/dist/app/modes/index.js +20 -10
  42. package/dist/app/modes/installation/agent.js +4 -5
  43. package/dist/app/modes/manage/agent.js +3 -4
  44. package/dist/app/modes/manage/prompt.js +1 -2
  45. package/dist/app/modes/metadata.d.ts +31 -0
  46. package/dist/app/modes/metadata.js +35 -0
  47. package/dist/app/modes/plan/agent.js +5 -8
  48. package/dist/app/modes/plan/prompt.js +3 -3
  49. package/dist/app/permissions/runtime-approval-flow.d.ts +1 -5
  50. package/dist/app/permissions/runtime-approval-flow.js +0 -28
  51. package/dist/app/permissions/tool-gates.d.ts +2 -3
  52. package/dist/app/permissions/tool-gates.js +3 -81
  53. package/dist/app/prompts/com-api.js +1 -1
  54. package/dist/app/prompts/shared-guidelines.js +7 -3
  55. package/dist/app/providers/google-vertex-gateway.js +4 -51
  56. package/dist/app/providers/model-registry.d.ts +1 -0
  57. package/dist/app/providers/model-registry.js +39 -0
  58. package/dist/app/providers/model-resolver.js +10 -7
  59. package/dist/app/providers/register-shortcut-provider.js +6 -6
  60. package/dist/app/providers/shortcut-api-v2-transport.js +4 -0
  61. package/dist/app/providers/shortcut-llm-proxy-client.d.ts +2 -2
  62. package/dist/app/providers/shortcut-llm-proxy-client.js +1 -0
  63. package/dist/app/resources/package-manager.js +2 -123
  64. package/dist/app/resources/resource-patterns.d.ts +9 -0
  65. package/dist/app/resources/resource-patterns.js +120 -0
  66. package/dist/app/session/forked-agent.d.ts +2 -2
  67. package/dist/app/session/model-manager.js +3 -2
  68. package/dist/app/session/session-lifecycle.js +3 -2
  69. package/dist/app/session/session-models.d.ts +2 -0
  70. package/dist/app/session/session-models.js +14 -6
  71. package/dist/app/settings-manager.d.ts +3 -3
  72. package/dist/app/shared/tools/execute-code/mog.d.ts +3 -1
  73. package/dist/app/shared/tools/execute-code/mog.js +18 -2
  74. package/dist/app/tools/bash.js +3 -3
  75. package/dist/app/tools/execute-code/index.d.ts +1 -0
  76. package/dist/app/tools/execute-code/index.js +3 -1
  77. package/dist/app/tools/find-ignore-files.d.ts +3 -0
  78. package/dist/app/tools/find-ignore-files.js +33 -0
  79. package/dist/app/tools/find.js +2 -21
  80. package/dist/app/tools/llm-analysis.js +0 -2
  81. package/dist/app/tools/refresh-context.d.ts +1 -1
  82. package/dist/app/tools/session-settings/update-session-settings.js +2 -2
  83. package/dist/app/tools/switch-mode.js +2 -2
  84. package/dist/app/tools/take-screenshot.js +0 -2
  85. package/dist/app/tools/task/runner.js +1 -2
  86. package/dist/app/tools/task/task.d.ts +1 -3
  87. package/dist/app/tools/task/task.js +20 -40
  88. package/dist/app/tools/web-search.js +12 -2
  89. package/dist/autocomplete/core.d.ts +33 -0
  90. package/dist/autocomplete/core.js +113 -0
  91. package/dist/{tui → autocomplete}/fuzzy.d.ts +0 -1
  92. package/dist/{tui → autocomplete}/fuzzy.js +0 -1
  93. package/dist/{tui/autocomplete.d.ts → autocomplete/index.d.ts} +0 -1
  94. package/dist/{tui/autocomplete.js → autocomplete/index.js} +62 -158
  95. package/dist/cli/args.js +1 -1
  96. package/dist/cli/list-models.js +1 -1
  97. package/dist/cli.js +26227 -30480
  98. package/dist/constants.d.ts +0 -2
  99. package/dist/constants.js +0 -3
  100. package/dist/contracts/agent-session-store.d.ts +1 -1
  101. package/dist/contracts/agent-settings.d.ts +1 -0
  102. package/dist/contracts/agent-settings.js +2 -1
  103. package/dist/contracts/model-call.d.ts +1 -2
  104. package/dist/contracts/model-call.js +1 -2
  105. package/dist/contracts/model-stream.d.ts +1 -0
  106. package/dist/contracts/model-stream.js +1 -0
  107. package/dist/contracts/wire.d.ts +10 -7
  108. package/dist/contracts/wire.js +8 -0
  109. package/dist/core/agent-engine.js +1 -1
  110. package/dist/core/core-types.d.ts +1 -21
  111. package/dist/core/defaults.d.ts +1 -1
  112. package/dist/core/defaults.js +9 -1
  113. package/dist/core/prompts/agent-guidelines.d.ts +4 -5
  114. package/dist/core/prompts/agent-guidelines.js +28 -42
  115. package/dist/core/session-schema.d.ts +1 -1
  116. package/dist/embedded-agent/ai-invoke.d.ts +1 -2
  117. package/dist/embedded-agent/ai-invoke.js +2 -70
  118. package/dist/embedded-agent/anthropic-messages-transport.d.ts +4 -20
  119. package/dist/embedded-agent/anthropic-messages-transport.js +23 -786
  120. package/dist/embedded-agent/bootstrap.js +2 -2
  121. package/dist/embedded-agent/compose.js +8 -7
  122. package/dist/embedded-agent/gateway-base-url.d.ts +3 -0
  123. package/dist/embedded-agent/gateway-base-url.js +35 -0
  124. package/dist/embedded-agent/google-vertex-transport.d.ts +2 -11
  125. package/dist/embedded-agent/google-vertex-transport.js +20 -117
  126. package/dist/embedded-agent/host-runtime-options.d.ts +0 -8
  127. package/dist/embedded-agent/host-runtime-options.js +10 -2
  128. package/dist/embedded-agent/host-tools/invoke-llm/contract.d.ts +2 -2
  129. package/dist/embedded-agent/host-tools/invoke-llm/contract.js +4 -2
  130. package/dist/embedded-agent/host-tools/sandbox-llm/contract.d.ts +8 -3
  131. package/dist/embedded-agent/host-tools/sandbox-llm/contract.js +12 -8
  132. package/dist/embedded-agent/host-tools/sandbox-llm/index.d.ts +1 -1
  133. package/dist/embedded-agent/host-tools/sandbox-llm/index.js +1 -1
  134. package/dist/embedded-agent/host-tools/shared/index.d.ts +1 -1
  135. package/dist/embedded-agent/host-tools/shared/index.js +1 -1
  136. package/dist/embedded-agent/host-tools/shared/llm-stream-error.d.ts +7 -0
  137. package/dist/embedded-agent/host-tools/shared/llm-stream-error.js +10 -0
  138. package/dist/embedded-agent/host-tools/take-screenshot/contract.d.ts +0 -2
  139. package/dist/embedded-agent/host-tools/take-screenshot/contract.js +1 -3
  140. package/dist/embedded-agent/host-tools/take-screenshot/index.d.ts +1 -1
  141. package/dist/embedded-agent/host-tools/take-screenshot/index.js +1 -1
  142. package/dist/embedded-agent/host-tools/task/agents/clone.js +1 -2
  143. package/dist/embedded-agent/host-tools/task/contract.js +1 -1
  144. package/dist/embedded-agent/index.d.ts +3 -4
  145. package/dist/embedded-agent/index.js +2 -3
  146. package/dist/embedded-agent/model-selection.d.ts +0 -6
  147. package/dist/embedded-agent/model-selection.js +15 -8
  148. package/dist/embedded-agent/openai-completions-transport.d.ts +2 -11
  149. package/dist/embedded-agent/openai-completions-transport.js +20 -120
  150. package/dist/embedded-agent/openai-responses-transport.d.ts +2 -11
  151. package/dist/embedded-agent/openai-responses-transport.js +15 -170
  152. package/dist/embedded-agent/prompt/build.js +1 -2
  153. package/dist/embedded-agent/prompt/sections.d.ts +1 -1
  154. package/dist/embedded-agent/prompt/sections.js +2 -0
  155. package/dist/embedded-agent/provider-native-request.d.ts +29 -0
  156. package/dist/embedded-agent/provider-native-request.js +65 -0
  157. package/dist/embedded-agent/run-stats.d.ts +1 -1
  158. package/dist/embedded-agent/stream.d.ts +10 -96
  159. package/dist/embedded-agent/stream.js +31 -556
  160. package/dist/embedded-agent/suggestions.d.ts +1 -1
  161. package/dist/embedded-agent/suggestions.js +1 -1
  162. package/dist/embedded-agent/worker-bridge/host-client.d.ts +7 -0
  163. package/dist/embedded-agent/worker-bridge/host-client.js +15 -0
  164. package/dist/embedded-agent/worker-bridge/worker-port.js +17 -1
  165. package/dist/main.js +18 -14
  166. package/dist/rpc/index.d.ts +1 -1
  167. package/dist/rpc/rpc-approval-gates.js +1 -2
  168. package/dist/rpc/rpc-host.d.ts +4 -0
  169. package/dist/rpc/rpc-host.js +13 -24
  170. package/dist/rpc/rpc-types.d.ts +6 -17
  171. package/dist/shared/platform/open.d.ts +4 -1
  172. package/dist/shared/platform/open.js +14 -2
  173. package/dist/shell/approvals/runtime-approval-gates.js +1 -3
  174. package/dist/shell/components/selectors/model-selector.js +2 -1
  175. package/dist/shell/components/selectors/session-selector-search.js +1 -1
  176. package/dist/shell/components/selectors/thinking-selector.js +7 -6
  177. package/dist/shell/index.d.ts +0 -1
  178. package/dist/shell/index.js +0 -1
  179. package/dist/shell/interactive/interactive-mode-options.d.ts +10 -0
  180. package/dist/shell/interactive/interactive-mode.d.ts +6 -12
  181. package/dist/shell/interactive/interactive-mode.js +39 -158
  182. package/dist/shell/interactive/slash-commands.d.ts +0 -34
  183. package/dist/shell/interactive/slash-commands.js +0 -2
  184. package/dist/shell/links/local-file-link-input.d.ts +15 -0
  185. package/dist/shell/links/local-file-link-input.js +38 -0
  186. package/dist/shell/links/local-file-link.d.ts +7 -0
  187. package/dist/shell/links/local-file-link.js +35 -0
  188. package/dist/shell/links/local-file-opener.d.ts +16 -0
  189. package/dist/shell/links/local-file-opener.js +90 -0
  190. package/dist/shell/session-client.d.ts +0 -1
  191. package/dist/shell/session-client.js +0 -6
  192. package/dist/shell/status/update-check-scheduler.d.ts +30 -0
  193. package/dist/shell/status/update-check-scheduler.js +71 -0
  194. package/dist/shell/status/update-reminder-controller.d.ts +22 -0
  195. package/dist/shell/status/update-reminder-controller.js +40 -0
  196. package/dist/shell/status/update-reminder.d.ts +6 -0
  197. package/dist/shell/status/update-reminder.js +4 -0
  198. package/dist/shell/theme/dark.json +1 -2
  199. package/dist/shell/theme/light.json +1 -2
  200. package/dist/shell/theme/runtime.d.ts +2 -3
  201. package/dist/shell/theme/runtime.js +2 -6
  202. package/dist/shell/theme/theme-schema.json +1 -6
  203. package/dist/shortcut-model-catalog.d.ts +7 -2
  204. package/dist/shortcut-model-catalog.js +134 -10
  205. package/dist/startup/update-manager.d.ts +6 -6
  206. package/dist/startup/update-manager.js +23 -38
  207. package/dist/startup/update-prompt.d.ts +1 -0
  208. package/dist/startup/update-prompt.js +2 -2
  209. package/dist/tui/components/editor.d.ts +1 -1
  210. package/dist/tui/components/editor.js +1 -1
  211. package/dist/tui/components/markdown.js +15 -6
  212. package/dist/tui/components/settings-list.js +1 -1
  213. package/dist/tui/editor-component.d.ts +1 -1
  214. package/dist/tui/index.d.ts +7 -8
  215. package/dist/tui/index.js +7 -6
  216. package/dist/tui/tui.d.ts +2 -0
  217. package/dist/tui/tui.js +16 -1
  218. package/dist/tui/utils.d.ts +7 -0
  219. package/dist/tui/utils.js +58 -8
  220. package/native-app/main/index.js +3200 -48
  221. package/native-app/main/{photon_rs-SoVDgheu.js → photon_rs-XkYyYTVA.js} +2 -2
  222. package/native-app/package.json +1 -1
  223. package/native-app/preload/index.cjs +2 -0
  224. package/native-app/renderer/assets/{index-CFc-oZuS.js → index-B4jz6epR.js} +825 -155
  225. package/native-app/renderer/assets/{index-OhGE0UY4.css → index-C__g36ft.css} +412 -162
  226. package/native-app/renderer/assets/shortcutagent-DxFsGSkw.png +0 -0
  227. package/native-app/renderer/index.html +2 -2
  228. package/package.json +17 -3
  229. package/skills/add-custom-model/SKILL.md +52 -0
  230. package/user-docs/dist/index.html +1 -1
  231. package/user-docs/dist/shortcutxl-docs.pdf +0 -0
  232. package/xll/ShortcutXL.xll +0 -0
  233. package/xll/modules/shortcut_xl/_managed.py +26 -7
  234. package/xll/modules/shortcut_xl/_tracking.py +141 -251
  235. package/xll/modules/shortcut_xl/api/__init__.py +60 -0
  236. package/xll/modules/shortcut_xl/api/workbook.py +9 -1
  237. package/xll/modules/shortcut_xl/api/worksheet.py +4 -1
  238. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  239. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  240. package/xll/python/Lib/site-packages/pip/__init__.py +1 -1
  241. package/xll/python/Lib/site-packages/pip/_internal/build_env/__init__.py +25 -0
  242. package/xll/python/Lib/site-packages/pip/_internal/build_env/base.py +103 -0
  243. package/xll/python/Lib/site-packages/pip/_internal/build_env/installer.py +333 -0
  244. package/xll/python/Lib/site-packages/pip/_internal/build_env/noop.py +42 -0
  245. package/xll/python/Lib/site-packages/pip/_internal/build_env/venv.py +145 -0
  246. package/xll/python/Lib/site-packages/pip/_internal/build_env/virtual.py +139 -0
  247. package/xll/python/Lib/site-packages/pip/_internal/cache.py +4 -1
  248. package/xll/python/Lib/site-packages/pip/_internal/cli/autocompletion.py +4 -1
  249. package/xll/python/Lib/site-packages/pip/_internal/cli/base_command.py +11 -14
  250. package/xll/python/Lib/site-packages/pip/_internal/cli/cmdoptions.py +133 -22
  251. package/xll/python/Lib/site-packages/pip/_internal/cli/index_command.py +11 -6
  252. package/xll/python/Lib/site-packages/pip/_internal/cli/main.py +2 -1
  253. package/xll/python/Lib/site-packages/pip/_internal/cli/main_parser.py +1 -2
  254. package/xll/python/Lib/site-packages/pip/_internal/cli/parser.py +10 -2
  255. package/xll/python/Lib/site-packages/pip/_internal/cli/progress_bars.py +2 -2
  256. package/xll/python/Lib/site-packages/pip/_internal/cli/req_command.py +57 -25
  257. package/xll/python/Lib/site-packages/pip/_internal/cli/status_codes.py +1 -0
  258. package/xll/python/Lib/site-packages/pip/_internal/commands/cache.py +7 -6
  259. package/xll/python/Lib/site-packages/pip/_internal/commands/configuration.py +2 -1
  260. package/xll/python/Lib/site-packages/pip/_internal/commands/debug.py +4 -5
  261. package/xll/python/Lib/site-packages/pip/_internal/commands/download.py +4 -0
  262. package/xll/python/Lib/site-packages/pip/_internal/commands/freeze.py +1 -1
  263. package/xll/python/Lib/site-packages/pip/_internal/commands/index.py +10 -3
  264. package/xll/python/Lib/site-packages/pip/_internal/commands/inspect.py +1 -1
  265. package/xll/python/Lib/site-packages/pip/_internal/commands/install.py +6 -6
  266. package/xll/python/Lib/site-packages/pip/_internal/commands/list.py +11 -6
  267. package/xll/python/Lib/site-packages/pip/_internal/commands/lock.py +4 -6
  268. package/xll/python/Lib/site-packages/pip/_internal/commands/show.py +14 -8
  269. package/xll/python/Lib/site-packages/pip/_internal/commands/wheel.py +7 -1
  270. package/xll/python/Lib/site-packages/pip/_internal/configuration.py +2 -3
  271. package/xll/python/Lib/site-packages/pip/_internal/distributions/base.py +5 -3
  272. package/xll/python/Lib/site-packages/pip/_internal/distributions/installed.py +3 -2
  273. package/xll/python/Lib/site-packages/pip/_internal/distributions/sdist.py +45 -20
  274. package/xll/python/Lib/site-packages/pip/_internal/distributions/wheel.py +3 -2
  275. package/xll/python/Lib/site-packages/pip/_internal/exceptions.py +174 -0
  276. package/xll/python/Lib/site-packages/pip/_internal/index/collector.py +70 -48
  277. package/xll/python/Lib/site-packages/pip/_internal/index/package_finder.py +46 -7
  278. package/xll/python/Lib/site-packages/pip/_internal/index/sources.py +1 -2
  279. package/xll/python/Lib/site-packages/pip/_internal/locations/_sysconfig.py +1 -1
  280. package/xll/python/Lib/site-packages/pip/_internal/locations/base.py +1 -1
  281. package/xll/python/Lib/site-packages/pip/_internal/metadata/base.py +10 -5
  282. package/xll/python/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py +1 -1
  283. package/xll/python/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py +8 -3
  284. package/xll/python/Lib/site-packages/pip/_internal/models/candidate.py +5 -1
  285. package/xll/python/Lib/site-packages/pip/_internal/models/link.py +53 -13
  286. package/xll/python/Lib/site-packages/pip/_internal/network/auth.py +38 -21
  287. package/xll/python/Lib/site-packages/pip/_internal/network/cache.py +2 -2
  288. package/xll/python/Lib/site-packages/pip/_internal/network/download.py +58 -11
  289. package/xll/python/Lib/site-packages/pip/_internal/network/session.py +35 -40
  290. package/xll/python/Lib/site-packages/pip/_internal/network/utils.py +109 -1
  291. package/xll/python/Lib/site-packages/pip/_internal/operations/check.py +2 -3
  292. package/xll/python/Lib/site-packages/pip/_internal/operations/install/wheel.py +23 -18
  293. package/xll/python/Lib/site-packages/pip/_internal/operations/prepare.py +152 -7
  294. package/xll/python/Lib/site-packages/pip/_internal/req/__init__.py +2 -0
  295. package/xll/python/Lib/site-packages/pip/_internal/req/constructors.py +54 -69
  296. package/xll/python/Lib/site-packages/pip/_internal/req/req_file.py +6 -6
  297. package/xll/python/Lib/site-packages/pip/_internal/req/req_install.py +25 -21
  298. package/xll/python/Lib/site-packages/pip/_internal/req/req_uninstall.py +13 -8
  299. package/xll/python/Lib/site-packages/pip/_internal/resolution/base.py +2 -2
  300. package/xll/python/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py +2 -2
  301. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py +1 -1
  302. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py +0 -1
  303. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py +39 -52
  304. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py +3 -3
  305. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py +3 -0
  306. package/xll/python/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py +9 -0
  307. package/xll/python/Lib/site-packages/pip/_internal/utils/compat.py +15 -9
  308. package/xll/python/Lib/site-packages/pip/_internal/utils/deprecation.py +6 -1
  309. package/xll/python/Lib/site-packages/pip/_internal/utils/filesystem.py +2 -2
  310. package/xll/python/Lib/site-packages/pip/_internal/utils/logging.py +11 -3
  311. package/xll/python/Lib/site-packages/pip/_internal/utils/misc.py +48 -4
  312. package/xll/python/Lib/site-packages/pip/_internal/utils/pylock.py +18 -2
  313. package/xll/python/Lib/site-packages/pip/_internal/utils/retry.py +2 -1
  314. package/xll/python/Lib/site-packages/pip/_internal/utils/subprocess.py +4 -3
  315. package/xll/python/Lib/site-packages/pip/_internal/utils/temp_dir.py +1 -2
  316. package/xll/python/Lib/site-packages/pip/_internal/utils/unpacking.py +30 -4
  317. package/xll/python/Lib/site-packages/pip/_internal/utils/urls.py +5 -2
  318. package/xll/python/Lib/site-packages/pip/_internal/utils/virtualenv.py +2 -36
  319. package/xll/python/Lib/site-packages/pip/_internal/vcs/git.py +8 -5
  320. package/xll/python/Lib/site-packages/pip/_internal/vcs/versioncontrol.py +1 -2
  321. package/xll/python/Lib/site-packages/pip/_internal/wheel_builder.py +5 -1
  322. package/xll/python/Lib/site-packages/pip/_vendor/bom.cdx.json +231 -0
  323. package/xll/python/Lib/site-packages/pip/_vendor/certifi/__init__.py +1 -1
  324. package/xll/python/Lib/site-packages/pip/_vendor/certifi/cacert.pem +0 -631
  325. package/xll/python/Lib/site-packages/pip/_vendor/distlib/__init__.py +1 -1
  326. package/xll/python/Lib/site-packages/pip/_vendor/distlib/compat.py +34 -64
  327. package/xll/python/Lib/site-packages/pip/_vendor/distlib/resources.py +28 -14
  328. package/xll/python/Lib/site-packages/pip/_vendor/distlib/scripts.py +7 -3
  329. package/xll/python/Lib/site-packages/pip/_vendor/distlib/util.py +48 -11
  330. package/xll/python/Lib/site-packages/pip/_vendor/idna/LICENSE.md +1 -1
  331. package/xll/python/Lib/site-packages/pip/_vendor/idna/__main__.py +6 -0
  332. package/xll/python/Lib/site-packages/pip/_vendor/idna/cli.py +128 -0
  333. package/xll/python/Lib/site-packages/pip/_vendor/idna/codec.py +50 -13
  334. package/xll/python/Lib/site-packages/pip/_vendor/idna/compat.py +26 -0
  335. package/xll/python/Lib/site-packages/pip/_vendor/idna/core.py +352 -141
  336. package/xll/python/Lib/site-packages/pip/_vendor/idna/idnadata.py +571 -2983
  337. package/xll/python/Lib/site-packages/pip/_vendor/idna/intranges.py +5 -7
  338. package/xll/python/Lib/site-packages/pip/_vendor/idna/package_data.py +1 -1
  339. package/xll/python/Lib/site-packages/pip/_vendor/idna/uts46data.py +16887 -8832
  340. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/__init__.py +352 -66
  341. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/__main__.py +11 -0
  342. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/_xdg.py +171 -0
  343. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/android.py +92 -41
  344. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/api.py +213 -49
  345. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/macos.py +128 -51
  346. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/unix.py +176 -127
  347. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/version.py +8 -18
  348. package/xll/python/Lib/site-packages/pip/_vendor/platformdirs/windows.py +185 -68
  349. package/xll/python/Lib/site-packages/pip/_vendor/pygments/__init__.py +2 -2
  350. package/xll/python/Lib/site-packages/pip/_vendor/pygments/__main__.py +1 -1
  351. package/xll/python/Lib/site-packages/pip/_vendor/pygments/console.py +1 -1
  352. package/xll/python/Lib/site-packages/pip/_vendor/pygments/filter.py +1 -1
  353. package/xll/python/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py +10 -8
  354. package/xll/python/Lib/site-packages/pip/_vendor/pygments/formatter.py +1 -1
  355. package/xll/python/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py +1 -1
  356. package/xll/python/Lib/site-packages/pip/_vendor/pygments/lexer.py +7 -5
  357. package/xll/python/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py +1 -1
  358. package/xll/python/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py +9 -8
  359. package/xll/python/Lib/site-packages/pip/_vendor/pygments/lexers/python.py +35 -32
  360. package/xll/python/Lib/site-packages/pip/_vendor/pygments/modeline.py +1 -1
  361. package/xll/python/Lib/site-packages/pip/_vendor/pygments/plugin.py +3 -1
  362. package/xll/python/Lib/site-packages/pip/_vendor/pygments/regexopt.py +13 -2
  363. package/xll/python/Lib/site-packages/pip/_vendor/pygments/scanner.py +1 -1
  364. package/xll/python/Lib/site-packages/pip/_vendor/pygments/sphinxext.py +1 -1
  365. package/xll/python/Lib/site-packages/pip/_vendor/pygments/style.py +1 -1
  366. package/xll/python/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py +1 -1
  367. package/xll/python/Lib/site-packages/pip/_vendor/pygments/token.py +1 -1
  368. package/xll/python/Lib/site-packages/pip/_vendor/pygments/unistring.py +1 -1
  369. package/xll/python/Lib/site-packages/pip/_vendor/pygments/util.py +2 -2
  370. package/xll/python/Lib/site-packages/pip/_vendor/requests/__init__.py +52 -16
  371. package/xll/python/Lib/site-packages/pip/_vendor/requests/__version__.py +2 -2
  372. package/xll/python/Lib/site-packages/pip/_vendor/requests/_internal_utils.py +2 -2
  373. package/xll/python/Lib/site-packages/pip/_vendor/requests/_types.py +183 -0
  374. package/xll/python/Lib/site-packages/pip/_vendor/requests/adapters.py +101 -50
  375. package/xll/python/Lib/site-packages/pip/_vendor/requests/api.py +31 -8
  376. package/xll/python/Lib/site-packages/pip/_vendor/requests/auth.py +80 -40
  377. package/xll/python/Lib/site-packages/pip/_vendor/requests/compat.py +9 -3
  378. package/xll/python/Lib/site-packages/pip/_vendor/requests/cookies.py +143 -79
  379. package/xll/python/Lib/site-packages/pip/_vendor/requests/exceptions.py +15 -5
  380. package/xll/python/Lib/site-packages/pip/_vendor/requests/help.py +13 -10
  381. package/xll/python/Lib/site-packages/pip/_vendor/requests/hooks.py +23 -9
  382. package/xll/python/Lib/site-packages/pip/_vendor/requests/models.py +265 -126
  383. package/xll/python/Lib/site-packages/pip/_vendor/requests/py.typed +0 -0
  384. package/xll/python/Lib/site-packages/pip/_vendor/requests/sessions.py +164 -78
  385. package/xll/python/Lib/site-packages/pip/_vendor/requests/status_codes.py +2 -2
  386. package/xll/python/Lib/site-packages/pip/_vendor/requests/structures.py +51 -20
  387. package/xll/python/Lib/site-packages/pip/_vendor/requests/utils.py +161 -89
  388. package/xll/python/Lib/site-packages/pip/_vendor/tomli/__init__.py +1 -1
  389. package/xll/python/Lib/site-packages/pip/_vendor/tomli/_parser.py +9 -4
  390. package/xll/python/Lib/site-packages/pip/_vendor/tomli/_re.py +13 -9
  391. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/_base_connection.py +3 -1
  392. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/_collections.py +0 -1
  393. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/_version.py +8 -18
  394. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/connection.py +4 -4
  395. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py +23 -10
  396. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/contrib/emscripten/response.py +5 -1
  397. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py +7 -8
  398. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py +1 -1
  399. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/exceptions.py +2 -2
  400. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/fields.py +14 -14
  401. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py +8 -6
  402. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/response.py +35 -22
  403. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/util/retry.py +8 -0
  404. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py +11 -61
  405. package/xll/python/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py +5 -11
  406. package/xll/python/Lib/site-packages/pip/_vendor/vendor.txt +8 -8
  407. package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/METADATA +3 -2
  408. package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/RECORD +208 -188
  409. package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/AUTHORS.txt +20 -0
  410. package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  411. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  412. package/xll/python/Scripts/httpx.exe +0 -0
  413. package/xll/python/Scripts/idna.exe +0 -0
  414. package/xll/python/Scripts/pip.exe +0 -0
  415. package/xll/python/Scripts/pip3.13.exe +0 -0
  416. package/xll/python/Scripts/pip3.exe +0 -0
  417. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  418. package/xll/python/Scripts/pywin32_testall.exe +0 -0
  419. package/dist/app/collab/awareness.d.ts +0 -32
  420. package/dist/app/collab/awareness.js +0 -52
  421. package/dist/app/collab/collab-client.d.ts +0 -37
  422. package/dist/app/collab/collab-client.js +0 -208
  423. package/dist/app/collab/collab-manager.d.ts +0 -91
  424. package/dist/app/collab/collab-manager.js +0 -218
  425. package/dist/app/collab/collab-sync.d.ts +0 -62
  426. package/dist/app/collab/collab-sync.js +0 -215
  427. package/dist/app/collab/index.d.ts +0 -9
  428. package/dist/app/collab/index.js +0 -6
  429. package/dist/app/collab/super-fight.d.ts +0 -90
  430. package/dist/app/collab/super-fight.js +0 -310
  431. package/dist/app/collab/sync-bridge.d.ts +0 -35
  432. package/dist/app/collab/sync-bridge.js +0 -109
  433. package/dist/app/collab/types.d.ts +0 -75
  434. package/dist/app/collab/types.js +0 -5
  435. package/dist/embedded-agent/ai-content.d.ts +0 -52
  436. package/dist/embedded-agent/ai-content.js +0 -101
  437. package/dist/embedded-agent/host-tools/shared/llm-stream.d.ts +0 -31
  438. package/dist/embedded-agent/host-tools/shared/llm-stream.js +0 -136
  439. package/dist/embedded-agent/model-call-orchestrator.d.ts +0 -22
  440. package/dist/embedded-agent/model-call-orchestrator.js +0 -22
  441. package/dist/embedded-agent/stream-errors.d.ts +0 -10
  442. package/dist/embedded-agent/stream-errors.js +0 -73
  443. package/dist/embedded-agent/stream-message-state.d.ts +0 -14
  444. package/dist/embedded-agent/stream-message-state.js +0 -271
  445. package/dist/embedded-agent/stream-sse.d.ts +0 -78
  446. package/dist/embedded-agent/stream-sse.js +0 -73
  447. package/dist/shell/approvals/task-spawn-approval.d.ts +0 -20
  448. package/dist/shell/approvals/task-spawn-approval.js +0 -93
  449. package/dist/shell/interactive/collab-command.d.ts +0 -9
  450. package/dist/shell/interactive/collab-command.js +0 -115
  451. package/dist/shell/interactive/super-fight-ui.d.ts +0 -80
  452. package/dist/shell/interactive/super-fight-ui.js +0 -484
  453. package/dist/shell/status/collab-status.d.ts +0 -18
  454. package/dist/shell/status/collab-status.js +0 -46
  455. package/xll/python/Lib/site-packages/pip/_internal/build_env.py +0 -606
  456. /package/dist/{tui → autocomplete}/skill-autocomplete.js +0 -0
  457. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/INSTALLER +0 -0
  458. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/REQUESTED +0 -0
  459. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/WHEEL +0 -0
  460. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/entry_points.txt +0 -0
  461. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/LICENSE.txt +0 -0
  462. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  463. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  464. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  465. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  466. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  467. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  468. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  469. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  470. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  471. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  472. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  473. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  474. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  475. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  476. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  477. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  478. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  479. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  480. /package/xll/python/Lib/site-packages/{pip-26.1.2.dist-info → pip-26.2.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Shortcut CLI</title>
7
- <script type="module" crossorigin src="./assets/index-CFc-oZuS.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-OhGE0UY4.css">
7
+ <script type="module" crossorigin src="./assets/index-B4jz6epR.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-C__g36ft.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shortcutxl",
3
- "version": "0.3.76",
3
+ "version": "0.3.78",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -33,6 +33,10 @@
33
33
  "types": "./dist/app/file-uploads.d.ts",
34
34
  "default": "./dist/app/file-uploads.js"
35
35
  },
36
+ "./app/tool-binaries": {
37
+ "types": "./dist/app/tools/tool-binaries.d.ts",
38
+ "default": "./dist/app/tools/tool-binaries.js"
39
+ },
36
40
  "./app/providers/message-converter": {
37
41
  "types": "./dist/app/providers/message-converter.d.ts",
38
42
  "default": "./dist/app/providers/message-converter.js"
@@ -145,6 +149,14 @@
145
149
  "types": "./dist/tui/index.d.ts",
146
150
  "default": "./dist/tui/index.js"
147
151
  },
152
+ "./autocomplete": {
153
+ "types": "./dist/autocomplete/index.d.ts",
154
+ "default": "./dist/autocomplete/index.js"
155
+ },
156
+ "./autocomplete/core": {
157
+ "types": "./dist/autocomplete/core.d.ts",
158
+ "default": "./dist/autocomplete/core.js"
159
+ },
148
160
  "./host-tools": {
149
161
  "types": "./dist/embedded-agent/host-tools/index.d.ts",
150
162
  "default": "./dist/embedded-agent/host-tools/index.js"
@@ -255,12 +267,11 @@
255
267
  "electron": "42.6.1",
256
268
  "extract-zip": "^2.0.1",
257
269
  "file-type": "^21.3.1",
258
- "glob": "^13.0.6",
259
270
  "hosted-git-info": "^9.0.2",
260
271
  "ignore": "^7.0.5",
261
272
  "jose": "^6.2.2",
262
273
  "marked": "^15.0.12",
263
- "minimatch": "^10.2.4",
274
+ "minimatch": "^10.2.6",
264
275
  "openai": "6.10.0",
265
276
  "partial-json": "^0.1.7",
266
277
  "proper-lockfile": "^4.1.2",
@@ -342,8 +353,11 @@
342
353
  "test": "pnpm run build:mogruntime && vitest --run",
343
354
  "dev:integration": "tsx tests/connectors/runner/run-agentic-integration.ts",
344
355
  "test:e2e": "vitest --run --config vitest.e2e.config.ts",
356
+ "test:e2e:provider-native-agentic": "vitest --run --config vitest.e2e.config.ts tests/embedded-agent/provider-native-agentic",
345
357
  "test:manual-smoke:whatsapp-live": "vitest --run --config vitest.e2e.config.ts tests/remote-control/whatsapp-cli.live-provider.manual-smoke.test.ts",
346
358
  "test:pty": "vitest --run --config vitest.pty.config.ts",
359
+ "test:pty:windows-smoke": "vitest --run --config vitest.pty.config.ts tests/helpers/pty/driver.pty.test.ts",
360
+ "test:windows-approvals": "vitest --run --config vitest.windows-approvals.config.ts",
347
361
  "test:tui-sim": "vitest --run --config vitest.tui-sim.config.ts",
348
362
  "smoke:rpc": "node scripts/smoke-rpc-host.js",
349
363
  "analyze:tui-trace": "node scripts/analyze-tui-render-trace.js",
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: add-custom-model
3
+ description: Add or update a custom model in ShortcutXL. Use when the user wants to connect a hosted model, local model, gateway, or proxy.
4
+ ---
5
+
6
+ # Add a Custom Model
7
+
8
+ Add the model to `~/.shortcut/agent/models.json`.
9
+
10
+ ## Steps
11
+
12
+ 1. Read `models.md` from the Agent Docs `Additional docs` path in the system prompt.
13
+ 2. Get the following details from the user or the provider's documentation:
14
+ - Provider ID
15
+ - Base URL
16
+ - API type
17
+ - Model ID
18
+ - API key or API-key environment variable
19
+ 3. Read the existing `models.json`. Preserve its current providers and models.
20
+ 4. Add or update the requested provider and model.
21
+ 5. Validate the JSON.
22
+ 6. Open `/model` and select the new model. Opening `/model` reloads the file.
23
+ 7. Test a response and a tool call.
24
+
25
+ Use one of these API types:
26
+
27
+ - `openai-completions`
28
+ - `openai-responses`
29
+ - `anthropic-messages`
30
+ - `google-generative-ai`
31
+
32
+ Use this minimal configuration:
33
+
34
+ ```json
35
+ {
36
+ "providers": {
37
+ "provider-id": {
38
+ "baseUrl": "https://provider.example/v1",
39
+ "api": "openai-completions",
40
+ "apiKey": "PROVIDER_API_KEY",
41
+ "models": [{ "id": "model-id" }]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ Prefer an environment-variable name for `apiKey` and confirm that it is set.
48
+ For a local server without authentication, use a non-empty placeholder such as
49
+ `local`. Never expose an API key in the response.
50
+
51
+ If the provider does not support one of the listed APIs, it cannot be added
52
+ through `models.json`.
@@ -1908,7 +1908,7 @@ Excel automatically loads ShortcutXL on startup. No manual setup needed again.
1908
1908
  npm update -g shortcutxl
1909
1909
  ```
1910
1910
 
1911
- ShortcutXL tells you when a new version is available. The Excel add-in auto-syncs with the agent — no separate update needed.
1911
+ ShortcutXL checks for updates asynchronously every 12 hours. When a new version is available, restart the CLI and choose **Update now** in the startup prompt. The Excel add-in auto-syncs with the agent — no separate update needed.
1912
1912
 
1913
1913
 
1914
1914
 
Binary file
Binary file
@@ -6,7 +6,8 @@ from shortcut_xl._com import xl_app
6
6
  from shortcut_xl._exec_sandbox import active_exec_sandbox, build_safe_builtins
7
7
  from shortcut_xl._log import xl_log
8
8
  from shortcut_xl._threading import _run_on_main
9
- from shortcut_xl._tracking import DirtyTracker, TrackedApp
9
+ from shortcut_xl._tracking import DirtyTracker
10
+ from shortcut_xl.api import active_write_tracker
10
11
  from shortcut_xl.api.format import format_cell_diff
11
12
  from shortcut_xl.api.utils.helpers import (
12
13
  address_to_index,
@@ -39,6 +40,24 @@ class EditModeError(Exception):
39
40
  """Raised when Excel is in cell edit mode at request start."""
40
41
 
41
42
 
43
+ class _ExcelAppProxy:
44
+ """Transparent app facade whose module globals participate in the sandbox."""
45
+
46
+ __slots__ = ('_app',)
47
+
48
+ def __init__(self, app):
49
+ object.__setattr__(self, '_app', app)
50
+
51
+ def __getattr__(self, name):
52
+ return getattr(object.__getattribute__(self, '_app'), name)
53
+
54
+ def __setattr__(self, name, value):
55
+ setattr(object.__getattribute__(self, '_app'), name, value)
56
+
57
+ def __repr__(self):
58
+ return repr(object.__getattribute__(self, '_app'))
59
+
60
+
42
61
  def _is_edit_mode_error(e):
43
62
  """Check if a COM exception is the Excel cell-edit-mode failure."""
44
63
  if hasattr(e, 'hresult') and e.hresult == _EDIT_MODE_SCODE:
@@ -64,8 +83,8 @@ def run_managed(code_str):
64
83
  """Compile and execute user code on the main thread with Excel state managed.
65
84
 
66
85
  Preserves Calculation/Iteration settings, disables Events/Alerts,
67
- runs code with a tracking proxy, collects dirty cells, then
68
- restores Excel state.
86
+ runs code with range-scoped tracking for wrapped API writes, collects
87
+ dirty cells, then restores Excel state.
69
88
 
70
89
  Returns the list of dirty cells. Caller (_exec_entry) is responsible
71
90
  for CF highlighting, JSON response building, and stdout capture.
@@ -99,11 +118,11 @@ def run_managed(code_str):
99
118
  try:
100
119
  app.EnableEvents = False
101
120
  app.DisplayAlerts = False
102
- tracker = DirtyTracker(app)
103
- tracked = TrackedApp(app, tracker)
121
+ tracker = DirtyTracker()
122
+ managed_app = _ExcelAppProxy(app)
104
123
  safe_builtins = build_safe_builtins()
105
- ns = {**_USER_GLOBALS, 'app': tracked, '__builtins__': safe_builtins}
106
- with active_exec_sandbox(safe_builtins):
124
+ ns = {**_USER_GLOBALS, 'app': managed_app, '__builtins__': safe_builtins}
125
+ with active_write_tracker(tracker), active_exec_sandbox(safe_builtins):
107
126
  exec(code, ns)
108
127
  finally:
109
128
  # Collect changes BEFORE restoring calc-related state so we only see
@@ -1,310 +1,200 @@
1
- """Dirty cell tracking — lazy sheet snapshots.
1
+ """Range-scoped dirty-cell tracking for wrapped Excel API writes.
2
2
 
3
- Instead of snapshotting the entire workbook, we proxy Worksheet access
4
- so that a sheet's UsedRange.Value2 is captured on first touch. After
5
- execution we re-read only the touched sheets and diff. Cost is O(touched
6
- sheets) not O(workbook), and the agent gets raw COM objects for everything
7
- below the Worksheet level — zero overhead on Range reads/writes.
3
+ Wrapped write operations register their destination COM Range immediately
4
+ before mutation. The tracker snapshots only those ranges and compares them
5
+ after execution. Direct COM writes are intentionally outside this contract.
8
6
  """
9
7
 
10
8
  from typing import Any, TypedDict
11
9
 
12
10
  from shortcut_xl._log import xl_log
13
11
  from shortcut_xl.api.utils.com_utils import normalize_2d
14
- from shortcut_xl.api.utils.helpers import col_letter
12
+ from shortcut_xl.api.utils.helpers import address_to_index, col_letter
15
13
 
16
14
 
17
15
  class _DirtyCellBase(TypedDict):
18
- """Required fields produced by _diff_snapshots for every changed cell."""
16
+ """Required fields produced for every changed cell."""
19
17
 
20
18
  sheet: str
21
- address: str # e.g. "B5" — bare cell address, no sheet prefix
19
+ address: str
22
20
  oldValue: Any
23
21
  value: Any
24
22
 
25
23
 
26
24
  class DirtyCell(_DirtyCellBase, total=False):
27
- """A changed cell as produced by DirtyTracker.collect_changes().
25
+ formula: str
26
+ oldFormula: str
27
+ workbook: str
28
+ numberFormat: str
28
29
 
29
- Required: sheet, address, oldValue, value
30
- Optional fields are added during the enrichment pass in collect_changes().
31
- """
32
30
 
33
- formula: str # new formula (if cell has one after the change)
34
- oldFormula: str # previous formula (if cell had one before)
35
- workbook: str # workbook name — added during enrichment
36
- numberFormat: str # Excel number format string — added during enrichment
37
-
38
-
39
- class _SheetSnapshot:
40
- """Immutable snapshot of a worksheet's UsedRange values and formulas."""
31
+ class _RangeSnapshot:
32
+ """Immutable values and formulas for one explicit COM Range."""
41
33
 
42
34
  __slots__ = ('top_row', 'left_col', 'rows', 'cols', 'data', 'formulas')
43
35
 
44
- def __init__(self, ws):
45
- ur = ws.UsedRange
46
- self.top_row = ur.Row
47
- self.left_col = ur.Column
48
- self.rows = ur.Rows.Count
49
- self.cols = ur.Columns.Count
50
- self.data = normalize_2d(ur.Value, self.rows, self.cols)
36
+ def __init__(self, rng):
37
+ self.top_row = rng.Row
38
+ self.left_col = rng.Column
39
+ self.rows = rng.Rows.Count
40
+ self.cols = rng.Columns.Count
41
+ self.data = normalize_2d(rng.Value, self.rows, self.cols)
51
42
  try:
52
- self.formulas = normalize_2d(ur.Formula, self.rows, self.cols)
43
+ self.formulas = normalize_2d(rng.Formula, self.rows, self.cols)
53
44
  except Exception:
54
45
  self.formulas = None
55
46
 
47
+ def contains(self, row: int, col: int) -> bool:
48
+ return self.top_row <= row < self.top_row + self.rows and self.left_col <= col < self.left_col + self.cols
49
+
50
+ def value_at(self, row: int, col: int):
51
+ return self.data[row - self.top_row][col - self.left_col]
52
+
53
+ def formula_at(self, row: int, col: int):
54
+ return _formula_value(self, row - self.top_row, col - self.left_col)
56
55
 
57
- def _get_formula(snap, ri, ci):
58
- """Get formula string from snapshot, or None if no formula data."""
56
+
57
+ def _formula_value(snap, ri, ci):
59
58
  if snap.formulas is None:
60
59
  return None
61
60
  try:
62
- f = snap.formulas[ri][ci]
61
+ formula = snap.formulas[ri][ci]
63
62
  except (IndexError, TypeError):
64
63
  return None
65
- # COM returns the raw value for non-formula cells; only keep actual formulas
66
- if isinstance(f, str) and f.startswith('='):
67
- return f
64
+ if isinstance(formula, str) and formula.startswith('='):
65
+ return formula
68
66
  return None
69
67
 
70
68
 
71
- def _diff_snapshots(sheet_name: str, before: _SheetSnapshot, after: _SheetSnapshot) -> list[DirtyCell]:
72
- """Compare two _SheetSnapshots, return list of change dicts.
73
-
74
- Compares row-by-row first (tuple equality short-circuits) so
75
- unchanged rows cost almost nothing.
76
- """
69
+ def _diff_snapshots(sheet_name: str, before: _RangeSnapshot, after: _RangeSnapshot) -> list[DirtyCell]:
70
+ """Compare two snapshots, including formula-only changes."""
77
71
  changes = []
78
- # Same geometry — fast path
79
- if (
72
+ same_geometry = (
80
73
  before.top_row == after.top_row
81
74
  and before.left_col == after.left_col
82
75
  and before.rows == after.rows
83
76
  and before.cols == after.cols
84
- ):
85
- for ri in range(before.rows):
86
- if before.data[ri] == after.data[ri]:
87
- continue
88
- row_num = before.top_row + ri
89
- for ci in range(before.cols):
90
- old = before.data[ri][ci]
91
- new = after.data[ri][ci]
92
- if old != new:
93
- addr = f'{col_letter(before.left_col + ci - 1)}{row_num}'
94
- entry = {
95
- 'sheet': sheet_name,
96
- 'address': addr,
97
- 'oldValue': old,
98
- 'value': new,
99
- }
100
- formula = _get_formula(after, ri, ci)
101
- if formula:
102
- entry['formula'] = formula
103
- old_formula = _get_formula(before, ri, ci)
104
- if old_formula:
105
- entry['oldFormula'] = old_formula
106
- changes.append(entry)
107
- return changes
108
-
109
- # Different geometry — union bounding box
110
- min_r = min(before.top_row, after.top_row)
111
- min_c = min(before.left_col, after.left_col)
112
- max_r = max(before.top_row + before.rows - 1, after.top_row + after.rows - 1)
113
- max_c = max(before.left_col + before.cols - 1, after.left_col + after.cols - 1)
114
-
115
- def _get(snap, row, col):
116
- r = row - snap.top_row
117
- c = col - snap.left_col
118
- if 0 <= r < snap.rows and 0 <= c < snap.cols:
119
- return snap.data[r][c]
77
+ )
78
+ min_row = min(before.top_row, after.top_row)
79
+ min_col = min(before.left_col, after.left_col)
80
+ max_row = max(before.top_row + before.rows - 1, after.top_row + after.rows - 1)
81
+ max_col = max(before.left_col + before.cols - 1, after.left_col + after.cols - 1)
82
+
83
+ def get_value(snap, row, col):
84
+ if snap.contains(row, col):
85
+ return snap.value_at(row, col)
120
86
  return None
121
87
 
122
- def _get_formula_abs(snap, row, col):
123
- r = row - snap.top_row
124
- c = col - snap.left_col
125
- if 0 <= r < snap.rows and 0 <= c < snap.cols:
126
- return _get_formula(snap, r, c)
88
+ def get_formula(snap, row, col):
89
+ if snap.contains(row, col):
90
+ return snap.formula_at(row, col)
127
91
  return None
128
92
 
129
- for row in range(min_r, max_r + 1):
130
- for col in range(min_c, max_c + 1):
131
- old = _get(before, row, col)
132
- new = _get(after, row, col)
133
- if old != new:
134
- addr = f'{col_letter(col - 1)}{row}'
135
- entry = {
136
- 'sheet': sheet_name,
137
- 'address': addr,
138
- 'oldValue': old,
139
- 'value': new,
140
- }
141
- formula = _get_formula_abs(after, row, col)
142
- if formula:
143
- entry['formula'] = formula
144
- old_formula = _get_formula_abs(before, row, col)
145
- if old_formula:
146
- entry['oldFormula'] = old_formula
147
- changes.append(entry)
93
+ for row in range(min_row, max_row + 1):
94
+ if same_geometry:
95
+ row_index = row - before.top_row
96
+ if before.data[row_index] == after.data[row_index] and all(
97
+ _formula_value(before, row_index, col_index) == _formula_value(after, row_index, col_index)
98
+ for col_index in range(before.cols)
99
+ ):
100
+ continue
101
+ for col in range(min_col, max_col + 1):
102
+ old = get_value(before, row, col)
103
+ new = get_value(after, row, col)
104
+ old_formula = get_formula(before, row, col)
105
+ formula = get_formula(after, row, col)
106
+ if old == new and old_formula == formula:
107
+ continue
108
+ entry: DirtyCell = {
109
+ 'sheet': sheet_name,
110
+ 'address': f'{col_letter(col - 1)}{row}',
111
+ 'oldValue': old,
112
+ 'value': new,
113
+ }
114
+ if formula:
115
+ entry['formula'] = formula
116
+ if old_formula:
117
+ entry['oldFormula'] = old_formula
118
+ changes.append(entry)
148
119
  return changes
149
120
 
150
121
 
122
+ class _TrackedRange:
123
+ __slots__ = ('rng', 'workbook', 'sheet', 'before')
124
+
125
+ def __init__(self, rng, workbook: str, sheet: str, before: _RangeSnapshot):
126
+ self.rng = rng
127
+ self.workbook = workbook
128
+ self.sheet = sheet
129
+ self.before = before
130
+
131
+
151
132
  class DirtyTracker:
152
- """Snapshots sheets on first access, diffs after execution."""
133
+ """Snapshots only ranges registered by wrapped write operations."""
153
134
 
154
- __slots__ = ('_app', '_snapshots')
135
+ __slots__ = ('_ranges', '_keys', '_originals')
155
136
 
156
- def __init__(self, app):
157
- self._app = app
158
- self._snapshots = {} # name -> (ws, _SheetSnapshot)
137
+ def __init__(self):
138
+ self._ranges: list[_TrackedRange] = []
139
+ self._keys: set[tuple[str, str, int, int, int, int]] = set()
140
+ self._originals: dict[tuple[str, str, int, int], tuple[Any, str | None]] = {}
159
141
 
160
- def snapshot_sheet(self, ws):
161
- """Capture a sheet's data on first access. No-op on subsequent calls."""
142
+ def track_range(self, rng) -> None:
143
+ """Capture a destination range once, before its first exact-range write."""
162
144
  try:
163
- name = ws.Name
164
- except Exception:
165
- return
166
- if name not in self._snapshots:
167
- try:
168
- self._snapshots[name] = (ws, _SheetSnapshot(ws))
169
- except Exception as e:
170
- xl_log(f'DirtyTracker.snapshot_sheet({name}): {e}')
145
+ worksheet = rng.Parent
146
+ sheet = worksheet.Name
147
+ workbook = worksheet.Parent.Name
148
+ key = (workbook, sheet, rng.Row, rng.Column, rng.Rows.Count, rng.Columns.Count)
149
+ if key in self._keys:
150
+ return
151
+ before = _RangeSnapshot(rng)
152
+ self._keys.add(key)
153
+ self._ranges.append(_TrackedRange(rng, workbook, sheet, before))
154
+ for row in range(before.top_row, before.top_row + before.rows):
155
+ for col in range(before.left_col, before.left_col + before.cols):
156
+ cell_key = (workbook, sheet, row, col)
157
+ self._originals.setdefault(cell_key, (before.value_at(row, col), before.formula_at(row, col)))
158
+ except Exception as error:
159
+ xl_log(f'DirtyTracker.track_range failed: {error}')
171
160
 
172
161
  def collect_changes(self) -> list[DirtyCell]:
173
- """Re-read snapshotted sheets and return list of cell changes."""
174
- all_changes = []
175
- for name, (ws, before) in self._snapshots.items():
162
+ """Re-read registered ranges and return deduplicated final cell changes."""
163
+ candidates: dict[tuple[str, str, str], DirtyCell] = {}
164
+ uniform_formats: dict[tuple[str, str, str], str] = {}
165
+
166
+ for tracked in self._ranges:
176
167
  try:
177
- after = _SheetSnapshot(ws)
178
- changes = _diff_snapshots(name, before, after)
179
- # Enrich with workbook name + numberFormat
168
+ after = _RangeSnapshot(tracked.rng)
169
+ changes = _diff_snapshots(tracked.sheet, tracked.before, after)
180
170
  try:
181
- wb_name = ws.Parent.Name
171
+ number_format = tracked.rng.NumberFormat if changes else None
182
172
  except Exception:
183
- wb_name = None
173
+ number_format = None
184
174
  for entry in changes:
185
- if wb_name:
186
- entry['workbook'] = wb_name
187
- try:
188
- fmt = ws.Range(entry['address']).NumberFormat
189
- if fmt and fmt != 'General':
190
- entry['numberFormat'] = str(fmt)
191
- except Exception:
192
- pass
193
- all_changes.extend(changes)
194
- except Exception as e:
195
- xl_log(f'DirtyTracker.collect_changes({name}): {e}')
196
- return all_changes
197
-
198
-
199
- # ---------------------------------------------------------------------------
200
- # Thin proxy classes (Application → Workbook → Worksheet access only)
201
- # ---------------------------------------------------------------------------
202
-
203
-
204
- class TrackedApp:
205
- """Proxy around Application. Wraps workbook/sheet access to trigger
206
- snapshots; everything else passes straight through to COM."""
207
-
208
- _WB_ATTRS = frozenset(('ActiveWorkbook', 'ThisWorkbook'))
209
- _SHEET_ACCESSORS = frozenset(('Worksheets', 'Sheets'))
210
- _SHEET_DIRECT = frozenset(('ActiveSheet',))
211
- __slots__ = ('_app', '_tracker')
212
-
213
- def __init__(self, app, tracker):
214
- object.__setattr__(self, '_app', app)
215
- object.__setattr__(self, '_tracker', tracker)
216
-
217
- def __getattr__(self, name):
218
- val = getattr(object.__getattribute__(self, '_app'), name)
219
- tracker = object.__getattribute__(self, '_tracker')
220
- if name in TrackedApp._WB_ATTRS:
221
- return _TrackedWorkbook(val, tracker)
222
- if name == 'Workbooks':
223
- return _TrackedWorkbooks(val, tracker)
224
- if name in TrackedApp._SHEET_ACCESSORS:
225
- return _TrackedSheetAccessor(val, tracker)
226
- if name in TrackedApp._SHEET_DIRECT:
227
- tracker.snapshot_sheet(val)
228
- return val
229
- return val
230
-
231
- def __setattr__(self, name, value):
232
- setattr(object.__getattribute__(self, '_app'), name, value)
233
-
234
- def __repr__(self):
235
- return repr(object.__getattribute__(self, '_app'))
236
-
237
-
238
- class _TrackedWorkbooks:
239
- """Proxy for Workbooks collection."""
240
-
241
- __slots__ = ('_obj', '_tracker')
242
-
243
- def __init__(self, obj, tracker):
244
- object.__setattr__(self, '_obj', obj)
245
- object.__setattr__(self, '_tracker', tracker)
246
-
247
- def __call__(self, *args, **kwargs):
248
- wb = object.__getattribute__(self, '_obj')(*args, **kwargs)
249
- return _TrackedWorkbook(wb, object.__getattribute__(self, '_tracker'))
250
-
251
- def __getattr__(self, name):
252
- return getattr(object.__getattribute__(self, '_obj'), name)
253
-
254
- def __iter__(self):
255
- tracker = object.__getattribute__(self, '_tracker')
256
- for wb in object.__getattribute__(self, '_obj'):
257
- yield _TrackedWorkbook(wb, tracker)
258
-
259
-
260
- class _TrackedWorkbook:
261
- """Proxy for a Workbook — wraps Worksheet/Sheet access."""
262
-
263
- _SHEET_ACCESSORS = frozenset(('Worksheets', 'Sheets'))
264
- _SHEET_DIRECT = frozenset(('ActiveSheet',))
265
- __slots__ = ('_wb', '_tracker')
266
-
267
- def __init__(self, wb, tracker):
268
- object.__setattr__(self, '_wb', wb)
269
- object.__setattr__(self, '_tracker', tracker)
270
-
271
- def __getattr__(self, name):
272
- val = getattr(object.__getattribute__(self, '_wb'), name)
273
- tracker = object.__getattribute__(self, '_tracker')
274
- if name in _TrackedWorkbook._SHEET_ACCESSORS:
275
- return _TrackedSheetAccessor(val, tracker)
276
- if name in _TrackedWorkbook._SHEET_DIRECT:
277
- tracker.snapshot_sheet(val)
278
- return val
279
- return val
280
-
281
- def __setattr__(self, name, value):
282
- setattr(object.__getattribute__(self, '_wb'), name, value)
283
-
284
- def __repr__(self):
285
- return repr(object.__getattribute__(self, '_wb'))
286
-
287
-
288
- class _TrackedSheetAccessor:
289
- """Proxy for Worksheets/Sheets collection — snapshots each sheet
290
- the first time it is accessed, then returns the real COM Worksheet."""
291
-
292
- __slots__ = ('_obj', '_tracker')
293
-
294
- def __init__(self, obj, tracker):
295
- object.__setattr__(self, '_obj', obj)
296
- object.__setattr__(self, '_tracker', tracker)
297
-
298
- def __call__(self, *args, **kwargs):
299
- ws = object.__getattribute__(self, '_obj')(*args, **kwargs)
300
- object.__getattribute__(self, '_tracker').snapshot_sheet(ws)
301
- return ws # real COM object — no more proxying
302
-
303
- def __getattr__(self, name):
304
- return getattr(object.__getattribute__(self, '_obj'), name)
175
+ key = (tracked.workbook, tracked.sheet, entry['address'])
176
+ entry['workbook'] = tracked.workbook
177
+ candidates[key] = entry
178
+ if isinstance(number_format, str) and number_format != 'General':
179
+ uniform_formats[key] = number_format
180
+ except Exception as error:
181
+ xl_log(f'DirtyTracker.collect_changes({tracked.workbook}:{tracked.sheet}): {error}')
182
+
183
+ result = []
184
+ for (workbook, sheet, address), candidate in candidates.items():
185
+ row_index, col_index = address_to_index(address)
186
+ old_value, old_formula = self._originals[(workbook, sheet, row_index + 1, col_index + 1)]
187
+ new_formula = candidate.get('formula')
188
+ if old_value == candidate['value'] and old_formula == new_formula:
189
+ continue
305
190
 
306
- def __iter__(self):
307
- tracker = object.__getattribute__(self, '_tracker')
308
- for ws in object.__getattribute__(self, '_obj'):
309
- tracker.snapshot_sheet(ws)
310
- yield ws
191
+ candidate['oldValue'] = old_value
192
+ if old_formula:
193
+ candidate['oldFormula'] = old_formula
194
+ else:
195
+ candidate.pop('oldFormula', None)
196
+ number_format = uniform_formats.get((workbook, sheet, address))
197
+ if number_format:
198
+ candidate['numberFormat'] = number_format
199
+ result.append(candidate)
200
+ return result