shortcutxl 0.2.12 → 0.2.13

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 (110) hide show
  1. package/README.md +26 -26
  2. package/agent-docs/README.md +397 -397
  3. package/agent-docs/docs/compaction.md +390 -390
  4. package/agent-docs/docs/custom-provider.md +580 -580
  5. package/agent-docs/docs/extensions.md +1971 -1971
  6. package/agent-docs/docs/packages.md +209 -209
  7. package/agent-docs/docs/rpc.md +1317 -1317
  8. package/agent-docs/docs/sdk.md +962 -962
  9. package/agent-docs/docs/session.md +412 -412
  10. package/agent-docs/docs/termux.md +127 -127
  11. package/agent-docs/docs/tui.md +887 -887
  12. package/agent-docs/examples/README.md +25 -25
  13. package/agent-docs/examples/extensions/README.md +205 -205
  14. package/agent-docs/examples/extensions/antigravity-image-gen.ts +447 -447
  15. package/agent-docs/examples/extensions/auto-commit-on-exit.ts +49 -49
  16. package/agent-docs/examples/extensions/bash-spawn-hook.ts +30 -30
  17. package/agent-docs/examples/extensions/bookmark.ts +50 -50
  18. package/agent-docs/examples/extensions/built-in-tool-renderer.ts +256 -256
  19. package/agent-docs/examples/extensions/claude-rules.ts +86 -86
  20. package/agent-docs/examples/extensions/commands.ts +75 -75
  21. package/agent-docs/examples/extensions/confirm-destructive.ts +59 -59
  22. package/agent-docs/examples/extensions/custom-compaction.ts +126 -126
  23. package/agent-docs/examples/extensions/custom-footer.ts +63 -63
  24. package/agent-docs/examples/extensions/custom-header.ts +73 -73
  25. package/agent-docs/examples/extensions/custom-provider-anthropic/index.ts +660 -660
  26. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +362 -362
  27. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +88 -88
  28. package/agent-docs/examples/extensions/custom-provider-qwen-cli/index.ts +349 -349
  29. package/agent-docs/examples/extensions/dirty-repo-guard.ts +56 -56
  30. package/agent-docs/examples/extensions/doom-overlay/doom-component.ts +133 -133
  31. package/agent-docs/examples/extensions/doom-overlay/doom-keys.ts +108 -108
  32. package/agent-docs/examples/extensions/doom-overlay/index.ts +74 -74
  33. package/agent-docs/examples/extensions/dynamic-resources/index.ts +15 -15
  34. package/agent-docs/examples/extensions/dynamic-tools.ts +77 -77
  35. package/agent-docs/examples/extensions/event-bus.ts +43 -43
  36. package/agent-docs/examples/extensions/file-trigger.ts +41 -41
  37. package/agent-docs/examples/extensions/git-checkpoint.ts +53 -53
  38. package/agent-docs/examples/extensions/handoff.ts +155 -155
  39. package/agent-docs/examples/extensions/hello.ts +25 -25
  40. package/agent-docs/examples/extensions/inline-bash.ts +94 -94
  41. package/agent-docs/examples/extensions/input-transform.ts +43 -43
  42. package/agent-docs/examples/extensions/interactive-shell.ts +209 -209
  43. package/agent-docs/examples/extensions/mac-system-theme.ts +47 -47
  44. package/agent-docs/examples/extensions/message-renderer.ts +59 -59
  45. package/agent-docs/examples/extensions/minimal-mode.ts +430 -430
  46. package/agent-docs/examples/extensions/modal-editor.ts +90 -90
  47. package/agent-docs/examples/extensions/model-status.ts +31 -31
  48. package/agent-docs/examples/extensions/notify.ts +55 -55
  49. package/agent-docs/examples/extensions/overlay-qa-tests.ts +936 -936
  50. package/agent-docs/examples/extensions/overlay-test.ts +159 -159
  51. package/agent-docs/examples/extensions/permission-gate.ts +37 -37
  52. package/agent-docs/examples/extensions/pirate.ts +47 -47
  53. package/agent-docs/examples/extensions/plan-mode/index.ts +363 -363
  54. package/agent-docs/examples/extensions/preset.ts +418 -418
  55. package/agent-docs/examples/extensions/protected-paths.ts +30 -30
  56. package/agent-docs/examples/extensions/qna.ts +122 -122
  57. package/agent-docs/examples/extensions/question.ts +278 -278
  58. package/agent-docs/examples/extensions/questionnaire.ts +440 -440
  59. package/agent-docs/examples/extensions/rainbow-editor.ts +90 -90
  60. package/agent-docs/examples/extensions/reload-runtime.ts +37 -37
  61. package/agent-docs/examples/extensions/rpc-demo.ts +124 -124
  62. package/agent-docs/examples/extensions/sandbox/index.ts +324 -324
  63. package/agent-docs/examples/extensions/send-user-message.ts +97 -97
  64. package/agent-docs/examples/extensions/session-name.ts +27 -27
  65. package/agent-docs/examples/extensions/shutdown-command.ts +69 -69
  66. package/agent-docs/examples/extensions/snake.ts +343 -343
  67. package/agent-docs/examples/extensions/space-invaders.ts +566 -566
  68. package/agent-docs/examples/extensions/ssh.ts +233 -233
  69. package/agent-docs/examples/extensions/status-line.ts +40 -40
  70. package/agent-docs/examples/extensions/subagent/agents.ts +130 -130
  71. package/agent-docs/examples/extensions/subagent/index.ts +1068 -1068
  72. package/agent-docs/examples/extensions/summarize.ts +206 -206
  73. package/agent-docs/examples/extensions/system-prompt-header.ts +17 -17
  74. package/agent-docs/examples/extensions/timed-confirm.ts +72 -72
  75. package/agent-docs/examples/extensions/titlebar-spinner.ts +58 -58
  76. package/agent-docs/examples/extensions/todo.ts +314 -314
  77. package/agent-docs/examples/extensions/tool-override.ts +146 -146
  78. package/agent-docs/examples/extensions/tools.ts +145 -145
  79. package/agent-docs/examples/extensions/trigger-compact.ts +40 -40
  80. package/agent-docs/examples/extensions/truncated-tool.ts +194 -194
  81. package/agent-docs/examples/extensions/widget-placement.ts +17 -17
  82. package/agent-docs/examples/extensions/with-deps/index.ts +37 -37
  83. package/agent-docs/examples/rpc-extension-ui.ts +654 -654
  84. package/agent-docs/examples/sdk/01-minimal.ts +22 -22
  85. package/agent-docs/examples/sdk/02-custom-model.ts +48 -48
  86. package/agent-docs/examples/sdk/03-custom-prompt.ts +55 -55
  87. package/agent-docs/examples/sdk/04-skills.ts +53 -53
  88. package/agent-docs/examples/sdk/05-tools.ts +56 -56
  89. package/agent-docs/examples/sdk/06-extensions.ts +88 -88
  90. package/agent-docs/examples/sdk/07-context-files.ts +40 -40
  91. package/agent-docs/examples/sdk/08-prompt-templates.ts +47 -47
  92. package/agent-docs/examples/sdk/09-api-keys-and-oauth.ts +48 -48
  93. package/agent-docs/examples/sdk/10-settings.ts +54 -54
  94. package/agent-docs/examples/sdk/11-sessions.ts +48 -48
  95. package/agent-docs/examples/sdk/12-full-control.ts +82 -82
  96. package/agent-docs/examples/sdk/README.md +144 -144
  97. package/agent-docs/xll-spec.md +110 -110
  98. package/dist/core/auth-storage.js +21 -2
  99. package/package.json +1 -1
  100. package/xll/ShortcutXL.xll +0 -0
  101. package/xll/modules/debug_render.py +272 -272
  102. package/xll/modules/gameboy.py +241 -241
  103. package/xll/modules/pong.py +188 -188
  104. package/xll/modules/shortcut_xl/_diff_highlight.py +176 -0
  105. package/xll/modules/shortcut_xl/_log.py +12 -12
  106. package/xll/modules/shortcut_xl/_registry.py +44 -44
  107. package/xll/modules/stocks.py +100 -100
  108. /package/skills/{com-advanced-api → COM-advanced-api}/SKILL.md +0 -0
  109. /package/skills/{com-advanced-api → COM-advanced-api}/excel-type-library.py +0 -0
  110. /package/skills/{com-advanced-api → COM-advanced-api}/office-type-library.py +0 -0
@@ -1,144 +1,144 @@
1
- # SDK Examples
2
-
3
- Programmatic usage of shortcutxl via `createAgentSession()`.
4
-
5
- ## Examples
6
-
7
- | File | Description |
8
- |------|-------------|
9
- | `01-minimal.ts` | Simplest usage with all defaults |
10
- | `02-custom-model.ts` | Select model and thinking level |
11
- | `03-custom-prompt.ts` | Replace or modify system prompt |
12
- | `04-skills.ts` | Discover, filter, or replace skills |
13
- | `05-tools.ts` | Built-in tools, custom tools |
14
- | `06-extensions.ts` | Logging, blocking, result modification |
15
- | `07-context-files.ts` | AGENTS.md context files |
16
- | `08-slash-commands.ts` | File-based slash commands |
17
- | `09-api-keys-and-oauth.ts` | API key resolution, OAuth config |
18
- | `10-settings.ts` | Override compaction, retry, terminal settings |
19
- | `11-sessions.ts` | In-memory, persistent, continue, list sessions |
20
- | `12-full-control.ts` | Replace everything, no discovery |
21
-
22
- ## Running
23
-
24
- ```bash
25
- cd packages/coding-agent
26
- npx tsx examples/sdk/01-minimal.ts
27
- ```
28
-
29
- ## Quick Reference
30
-
31
- ```typescript
32
- import { getModel } from "shortcutxl";
33
- import {
34
- AuthStorage,
35
- createAgentSession,
36
- DefaultResourceLoader,
37
- ModelRegistry,
38
- SessionManager,
39
- SettingsManager,
40
- codingTools,
41
- readOnlyTools,
42
- readTool, bashTool, editTool, writeTool,
43
- } from "shortcutxl";
44
-
45
- // Auth and models setup
46
- const authStorage = AuthStorage.create();
47
- const modelRegistry = new ModelRegistry(authStorage);
48
-
49
- // Minimal
50
- const { session } = await createAgentSession({ authStorage, modelRegistry });
51
-
52
- // Custom model
53
- const model = getModel("anthropic", "claude-opus-4-5");
54
- const { session } = await createAgentSession({ model, thinkingLevel: "high", authStorage, modelRegistry });
55
-
56
- // Modify prompt
57
- const loader = new DefaultResourceLoader({
58
- systemPromptOverride: (base) => `${base}\n\nBe concise.`,
59
- });
60
- await loader.reload();
61
- const { session } = await createAgentSession({ resourceLoader: loader, authStorage, modelRegistry });
62
-
63
- // Read-only
64
- const { session } = await createAgentSession({ tools: readOnlyTools, authStorage, modelRegistry });
65
-
66
- // In-memory
67
- const { session } = await createAgentSession({
68
- sessionManager: SessionManager.inMemory(),
69
- authStorage,
70
- modelRegistry,
71
- });
72
-
73
- // Full control
74
- const customAuth = AuthStorage.create("/my/app/auth.json");
75
- customAuth.setRuntimeApiKey("anthropic", process.env.MY_KEY!);
76
- const customRegistry = new ModelRegistry(customAuth);
77
-
78
- const resourceLoader = new DefaultResourceLoader({
79
- systemPromptOverride: () => "You are helpful.",
80
- extensionFactories: [myExtension],
81
- skillsOverride: () => ({ skills: [], diagnostics: [] }),
82
- agentsFilesOverride: () => ({ agentsFiles: [] }),
83
- promptsOverride: () => ({ prompts: [], diagnostics: [] }),
84
- });
85
- await resourceLoader.reload();
86
-
87
- const { session } = await createAgentSession({
88
- model,
89
- authStorage: customAuth,
90
- modelRegistry: customRegistry,
91
- resourceLoader,
92
- tools: [readTool, bashTool],
93
- customTools: [{ tool: myTool }],
94
- sessionManager: SessionManager.inMemory(),
95
- settingsManager: SettingsManager.inMemory(),
96
- });
97
-
98
- // Run prompts
99
- session.subscribe((event) => {
100
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
101
- process.stdout.write(event.assistantMessageEvent.delta);
102
- }
103
- });
104
- await session.prompt("Hello");
105
- ```
106
-
107
- ## Options
108
-
109
- | Option | Default | Description |
110
- |--------|---------|-------------|
111
- | `authStorage` | `AuthStorage.create()` | Credential storage |
112
- | `modelRegistry` | `new ModelRegistry(authStorage)` | Model registry |
113
- | `cwd` | `process.cwd()` | Working directory |
114
- | `agentDir` | `~/.shortcut/agent` | Config directory |
115
- | `model` | From settings/first available | Model to use |
116
- | `thinkingLevel` | From settings/"off" | off, low, medium, high |
117
- | `tools` | `codingTools` | Built-in tools |
118
- | `customTools` | `[]` | Additional tool definitions |
119
- | `resourceLoader` | DefaultResourceLoader | Resource loader for extensions, skills, prompts, themes |
120
- | `sessionManager` | `SessionManager.create(cwd)` | Persistence |
121
- | `settingsManager` | `SettingsManager.create(cwd, agentDir)` | Settings overrides |
122
-
123
- ## Events
124
-
125
- ```typescript
126
- session.subscribe((event) => {
127
- switch (event.type) {
128
- case "message_update":
129
- if (event.assistantMessageEvent.type === "text_delta") {
130
- process.stdout.write(event.assistantMessageEvent.delta);
131
- }
132
- break;
133
- case "tool_execution_start":
134
- console.log(`Tool: ${event.toolName}`);
135
- break;
136
- case "tool_execution_end":
137
- console.log(`Result: ${event.result}`);
138
- break;
139
- case "agent_end":
140
- console.log("Done");
141
- break;
142
- }
143
- });
144
- ```
1
+ # SDK Examples
2
+
3
+ Programmatic usage of shortcutxl via `createAgentSession()`.
4
+
5
+ ## Examples
6
+
7
+ | File | Description |
8
+ |------|-------------|
9
+ | `01-minimal.ts` | Simplest usage with all defaults |
10
+ | `02-custom-model.ts` | Select model and thinking level |
11
+ | `03-custom-prompt.ts` | Replace or modify system prompt |
12
+ | `04-skills.ts` | Discover, filter, or replace skills |
13
+ | `05-tools.ts` | Built-in tools, custom tools |
14
+ | `06-extensions.ts` | Logging, blocking, result modification |
15
+ | `07-context-files.ts` | AGENTS.md context files |
16
+ | `08-slash-commands.ts` | File-based slash commands |
17
+ | `09-api-keys-and-oauth.ts` | API key resolution, OAuth config |
18
+ | `10-settings.ts` | Override compaction, retry, terminal settings |
19
+ | `11-sessions.ts` | In-memory, persistent, continue, list sessions |
20
+ | `12-full-control.ts` | Replace everything, no discovery |
21
+
22
+ ## Running
23
+
24
+ ```bash
25
+ cd packages/coding-agent
26
+ npx tsx examples/sdk/01-minimal.ts
27
+ ```
28
+
29
+ ## Quick Reference
30
+
31
+ ```typescript
32
+ import { getModel } from "shortcutxl";
33
+ import {
34
+ AuthStorage,
35
+ createAgentSession,
36
+ DefaultResourceLoader,
37
+ ModelRegistry,
38
+ SessionManager,
39
+ SettingsManager,
40
+ codingTools,
41
+ readOnlyTools,
42
+ readTool, bashTool, editTool, writeTool,
43
+ } from "shortcutxl";
44
+
45
+ // Auth and models setup
46
+ const authStorage = AuthStorage.create();
47
+ const modelRegistry = new ModelRegistry(authStorage);
48
+
49
+ // Minimal
50
+ const { session } = await createAgentSession({ authStorage, modelRegistry });
51
+
52
+ // Custom model
53
+ const model = getModel("anthropic", "claude-opus-4-5");
54
+ const { session } = await createAgentSession({ model, thinkingLevel: "high", authStorage, modelRegistry });
55
+
56
+ // Modify prompt
57
+ const loader = new DefaultResourceLoader({
58
+ systemPromptOverride: (base) => `${base}\n\nBe concise.`,
59
+ });
60
+ await loader.reload();
61
+ const { session } = await createAgentSession({ resourceLoader: loader, authStorage, modelRegistry });
62
+
63
+ // Read-only
64
+ const { session } = await createAgentSession({ tools: readOnlyTools, authStorage, modelRegistry });
65
+
66
+ // In-memory
67
+ const { session } = await createAgentSession({
68
+ sessionManager: SessionManager.inMemory(),
69
+ authStorage,
70
+ modelRegistry,
71
+ });
72
+
73
+ // Full control
74
+ const customAuth = AuthStorage.create("/my/app/auth.json");
75
+ customAuth.setRuntimeApiKey("anthropic", process.env.MY_KEY!);
76
+ const customRegistry = new ModelRegistry(customAuth);
77
+
78
+ const resourceLoader = new DefaultResourceLoader({
79
+ systemPromptOverride: () => "You are helpful.",
80
+ extensionFactories: [myExtension],
81
+ skillsOverride: () => ({ skills: [], diagnostics: [] }),
82
+ agentsFilesOverride: () => ({ agentsFiles: [] }),
83
+ promptsOverride: () => ({ prompts: [], diagnostics: [] }),
84
+ });
85
+ await resourceLoader.reload();
86
+
87
+ const { session } = await createAgentSession({
88
+ model,
89
+ authStorage: customAuth,
90
+ modelRegistry: customRegistry,
91
+ resourceLoader,
92
+ tools: [readTool, bashTool],
93
+ customTools: [{ tool: myTool }],
94
+ sessionManager: SessionManager.inMemory(),
95
+ settingsManager: SettingsManager.inMemory(),
96
+ });
97
+
98
+ // Run prompts
99
+ session.subscribe((event) => {
100
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
101
+ process.stdout.write(event.assistantMessageEvent.delta);
102
+ }
103
+ });
104
+ await session.prompt("Hello");
105
+ ```
106
+
107
+ ## Options
108
+
109
+ | Option | Default | Description |
110
+ |--------|---------|-------------|
111
+ | `authStorage` | `AuthStorage.create()` | Credential storage |
112
+ | `modelRegistry` | `new ModelRegistry(authStorage)` | Model registry |
113
+ | `cwd` | `process.cwd()` | Working directory |
114
+ | `agentDir` | `~/.shortcut/agent` | Config directory |
115
+ | `model` | From settings/first available | Model to use |
116
+ | `thinkingLevel` | From settings/"off" | off, low, medium, high |
117
+ | `tools` | `codingTools` | Built-in tools |
118
+ | `customTools` | `[]` | Additional tool definitions |
119
+ | `resourceLoader` | DefaultResourceLoader | Resource loader for extensions, skills, prompts, themes |
120
+ | `sessionManager` | `SessionManager.create(cwd)` | Persistence |
121
+ | `settingsManager` | `SettingsManager.create(cwd, agentDir)` | Settings overrides |
122
+
123
+ ## Events
124
+
125
+ ```typescript
126
+ session.subscribe((event) => {
127
+ switch (event.type) {
128
+ case "message_update":
129
+ if (event.assistantMessageEvent.type === "text_delta") {
130
+ process.stdout.write(event.assistantMessageEvent.delta);
131
+ }
132
+ break;
133
+ case "tool_execution_start":
134
+ console.log(`Tool: ${event.toolName}`);
135
+ break;
136
+ case "tool_execution_end":
137
+ console.log(`Result: ${event.result}`);
138
+ break;
139
+ case "agent_end":
140
+ console.log("Done");
141
+ break;
142
+ }
143
+ });
144
+ ```
@@ -1,110 +1,110 @@
1
- # ShortcutXL
2
-
3
- A native Excel add-in (`.xll`) written in C that turns Excel into a programmable surface for an AI agent. The agent runs locally on Windows as a TypeScript/Node.js CLI with a terminal TUI. It controls Excel through two channels: writing Python files for custom functions, and sending code blocks over HTTP for direct manipulation.
4
-
5
- See `../agent/PLAN.md` for the agent architecture (loop, multiagent, session management).
6
-
7
- ---
8
-
9
- ## Architecture
10
-
11
- ```
12
- ┌─ Windows ──────────────────────────────────────────┐
13
- │ │
14
- │ Terminal (PowerShell / Windows Terminal) │
15
- │ └─ Agent CLI (Node.js, TypeScript) │
16
- │ ├─ TUI (shortcutxl) │
17
- │ ├─ Supervisor agent loop │
18
- │ │ ├─ POST /exec → localhost:8080 ────────────┼──► HTTP
19
- │ │ ├─ writes .py → modules/ ──────────────────┼──► filesystem
20
- │ │ └─ Task tool → spawns worker agents │
21
- │ └─ shortcutxl (Anthropic, OpenAI, Google) │
22
- │ │
23
- │ Excel.exe │
24
- │ └─ ShortcutXL (in-process) │
25
- │ ├─ HTTP server (:8080) │
26
- │ │ ├─ GET /config (paths) │
27
- │ │ └─ POST /exec (run Python) │
28
- │ ├─ fs_watcher (hot-reload) │
29
- │ ├─ CPython 3.12 (embedded) │
30
- │ └─ COM → all open workbooks │
31
- │ │
32
- └─────────────────────────────────────────────────────┘
33
- ```
34
-
35
- On startup the agent calls `GET /config` to discover the `modules/` path and is ready to go. Both agent and Excel run natively on Windows — no WSL boundary, no filesystem translation.
36
-
37
- ---
38
-
39
- ## Two Channels
40
-
41
- ### 1. `POST /exec` — imperative actions
42
-
43
- Agent sends a Python code block. The XLL runs it in the embedded runtime with full COM access to Excel. The result (stdout + stderr) is returned.
44
-
45
- ```
46
- POST http://localhost:8080/exec
47
- {"code": "from shortcut_xl import xl_app\napp = xl_app()\napp.Range('A1:D1').Value = [['Name','Revenue','Growth','Status']]\napp.Range('A1:D1').Font.Bold = True"}
48
-
49
- → {"ok": true, "output": ""}
50
- ```
51
-
52
- Use this for: writing cells, reading data, formatting, creating charts, any one-shot action.
53
-
54
- ### 2. Filesystem — custom functions
55
-
56
- Agent writes `.py` files with `@xl_func` decorators to `modules/`. The XLL watches the directory, hot-reloads within ~200ms, and registers them as native Excel formulas.
57
-
58
- ```python
59
- # modules/dashboard.py
60
- from shortcut_xl import xl_func
61
-
62
- @xl_func
63
- def revenue_ytd(region):
64
- return {"EMEA": 1.2e6, "NA": 3.4e6}.get(region, 0)
65
- ```
66
-
67
- In Excel: `=revenue_ytd("EMEA")` → `1200000`
68
-
69
- Use this for: custom worksheet functions that Excel's calc engine calls repeatedly, tick loops, animations.
70
-
71
- ### Why two channels
72
-
73
- | Need | Channel | Reason |
74
- |---|---|---|
75
- | Write cells, format, charts | `/exec` | One-shot, result discarded after execution |
76
- | Custom formulas (`=foo()`) | filesystem | Excel calls them on every recalculation |
77
- | Tick loops, animations | filesystem | Persistent code running on a timer |
78
-
79
- HTTP gives the agent a simple request/response interface for one-shot operations. The filesystem channel handles the only thing HTTP can't: registering functions with Excel's calc engine via `xlfRegister`, which requires in-process code that persists between calls.
80
-
81
- ---
82
-
83
- ## What Runs Where
84
-
85
- | Component | Where | Why |
86
- |---|---|---|
87
- | Agent CLI (LLM calls, orchestration, TUI) | Windows, Node.js process | Native filesystem + HTTP to Excel, no cross-OS friction |
88
- | ShortcutXL (HTTP, fs_watcher, Python, COM) | Windows, inside excel.exe | In-process access to Excel's internals |
89
- | `modules/*.py` | Windows filesystem | Agent writes directly, XLL watches for changes |
90
-
91
- ---
92
-
93
- ## Installation
94
-
95
- **Installer (recommended):** MSI drops `ShortcutXL.xll` + embedded Python runtime. Writes registry key:
96
-
97
- ```
98
- HKCU\Software\Microsoft\Office\<version>\Excel\Options
99
- OPEN = /R "C:\Program Files\Shortcut\ShortcutXL.xll"
100
- ```
101
-
102
- Excel loads it automatically on every startup.
103
-
104
- **Manual:** Excel → File → Options → Add-ins → Manage: Excel Add-ins → Browse → select `ShortcutXL.xll`.
105
-
106
- ---
107
-
108
- ## Platform
109
-
110
- Windows only. The XLL API is a Windows DLL interface. The custom spreadsheet engine (see `new-sheet-spec.md`) is the cross-platform path.
1
+ # ShortcutXL
2
+
3
+ A native Excel add-in (`.xll`) written in C that turns Excel into a programmable surface for an AI agent. The agent runs locally on Windows as a TypeScript/Node.js CLI with a terminal TUI. It controls Excel through two channels: writing Python files for custom functions, and sending code blocks over HTTP for direct manipulation.
4
+
5
+ See `../agent/PLAN.md` for the agent architecture (loop, multiagent, session management).
6
+
7
+ ---
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ ┌─ Windows ──────────────────────────────────────────┐
13
+ │ │
14
+ │ Terminal (PowerShell / Windows Terminal) │
15
+ │ └─ Agent CLI (Node.js, TypeScript) │
16
+ │ ├─ TUI (shortcutxl) │
17
+ │ ├─ Supervisor agent loop │
18
+ │ │ ├─ POST /exec → localhost:8080 ────────────┼──► HTTP
19
+ │ │ ├─ writes .py → modules/ ──────────────────┼──► filesystem
20
+ │ │ └─ Task tool → spawns worker agents │
21
+ │ └─ shortcutxl (Anthropic, OpenAI, Google) │
22
+ │ │
23
+ │ Excel.exe │
24
+ │ └─ ShortcutXL (in-process) │
25
+ │ ├─ HTTP server (:8080) │
26
+ │ │ ├─ GET /config (paths) │
27
+ │ │ └─ POST /exec (run Python) │
28
+ │ ├─ fs_watcher (hot-reload) │
29
+ │ ├─ CPython 3.12 (embedded) │
30
+ │ └─ COM → all open workbooks │
31
+ │ │
32
+ └─────────────────────────────────────────────────────┘
33
+ ```
34
+
35
+ On startup the agent calls `GET /config` to discover the `modules/` path and is ready to go. Both agent and Excel run natively on Windows — no WSL boundary, no filesystem translation.
36
+
37
+ ---
38
+
39
+ ## Two Channels
40
+
41
+ ### 1. `POST /exec` — imperative actions
42
+
43
+ Agent sends a Python code block. The XLL runs it in the embedded runtime with full COM access to Excel. The result (stdout + stderr) is returned.
44
+
45
+ ```
46
+ POST http://localhost:8080/exec
47
+ {"code": "from shortcut_xl import xl_app\napp = xl_app()\napp.Range('A1:D1').Value = [['Name','Revenue','Growth','Status']]\napp.Range('A1:D1').Font.Bold = True"}
48
+
49
+ → {"ok": true, "output": ""}
50
+ ```
51
+
52
+ Use this for: writing cells, reading data, formatting, creating charts, any one-shot action.
53
+
54
+ ### 2. Filesystem — custom functions
55
+
56
+ Agent writes `.py` files with `@xl_func` decorators to `modules/`. The XLL watches the directory, hot-reloads within ~200ms, and registers them as native Excel formulas.
57
+
58
+ ```python
59
+ # modules/dashboard.py
60
+ from shortcut_xl import xl_func
61
+
62
+ @xl_func
63
+ def revenue_ytd(region):
64
+ return {"EMEA": 1.2e6, "NA": 3.4e6}.get(region, 0)
65
+ ```
66
+
67
+ In Excel: `=revenue_ytd("EMEA")` → `1200000`
68
+
69
+ Use this for: custom worksheet functions that Excel's calc engine calls repeatedly, tick loops, animations.
70
+
71
+ ### Why two channels
72
+
73
+ | Need | Channel | Reason |
74
+ |---|---|---|
75
+ | Write cells, format, charts | `/exec` | One-shot, result discarded after execution |
76
+ | Custom formulas (`=foo()`) | filesystem | Excel calls them on every recalculation |
77
+ | Tick loops, animations | filesystem | Persistent code running on a timer |
78
+
79
+ HTTP gives the agent a simple request/response interface for one-shot operations. The filesystem channel handles the only thing HTTP can't: registering functions with Excel's calc engine via `xlfRegister`, which requires in-process code that persists between calls.
80
+
81
+ ---
82
+
83
+ ## What Runs Where
84
+
85
+ | Component | Where | Why |
86
+ |---|---|---|
87
+ | Agent CLI (LLM calls, orchestration, TUI) | Windows, Node.js process | Native filesystem + HTTP to Excel, no cross-OS friction |
88
+ | ShortcutXL (HTTP, fs_watcher, Python, COM) | Windows, inside excel.exe | In-process access to Excel's internals |
89
+ | `modules/*.py` | Windows filesystem | Agent writes directly, XLL watches for changes |
90
+
91
+ ---
92
+
93
+ ## Installation
94
+
95
+ **Installer (recommended):** MSI drops `ShortcutXL.xll` + embedded Python runtime. Writes registry key:
96
+
97
+ ```
98
+ HKCU\Software\Microsoft\Office\<version>\Excel\Options
99
+ OPEN = /R "C:\Program Files\Shortcut\ShortcutXL.xll"
100
+ ```
101
+
102
+ Excel loads it automatically on every startup.
103
+
104
+ **Manual:** Excel → File → Options → Add-ins → Manage: Excel Add-ins → Browse → select `ShortcutXL.xll`.
105
+
106
+ ---
107
+
108
+ ## Platform
109
+
110
+ Windows only. The XLL API is a Windows DLL interface. The custom spreadsheet engine (see `new-sheet-spec.md`) is the cross-platform path.
@@ -11,6 +11,12 @@ import { dirname, join } from 'path';
11
11
  import lockfile from 'proper-lockfile';
12
12
  import { getAgentDir } from '../config.js';
13
13
  import { resolveConfigValue } from './resolve-config-value.js';
14
+ /**
15
+ * Shared lock options so sync and async locks always use the same lock path.
16
+ * realpath: false ensures both create the same .lock directory on Windows,
17
+ * where fs.realpath can normalize paths differently.
18
+ */
19
+ const LOCK_BASE_OPTIONS = { realpath: false };
14
20
  export class FileAuthStorageBackend {
15
21
  authPath;
16
22
  constructor(authPath = join(getAgentDir(), 'auth.json')) {
@@ -33,7 +39,7 @@ export class FileAuthStorageBackend {
33
39
  this.ensureFileExists();
34
40
  let release;
35
41
  try {
36
- release = lockfile.lockSync(this.authPath, { realpath: false });
42
+ release = lockfile.lockSync(this.authPath, LOCK_BASE_OPTIONS);
37
43
  const current = existsSync(this.authPath) ? readFileSync(this.authPath, 'utf-8') : undefined;
38
44
  const { result, next } = fn(current);
39
45
  if (next !== undefined) {
@@ -61,6 +67,7 @@ export class FileAuthStorageBackend {
61
67
  };
62
68
  try {
63
69
  release = await lockfile.lock(this.authPath, {
70
+ ...LOCK_BASE_OPTIONS,
64
71
  retries: {
65
72
  retries: 10,
66
73
  factor: 2,
@@ -245,7 +252,15 @@ export class AuthStorage {
245
252
  }
246
253
  try {
247
254
  this.storage.withLock((current) => {
248
- const currentData = this.parseStorageData(current);
255
+ // If the existing file is corrupt, start from empty rather than failing.
256
+ // This recovers from partial writes (e.g., process killed mid-save).
257
+ let currentData;
258
+ try {
259
+ currentData = this.parseStorageData(current);
260
+ }
261
+ catch {
262
+ currentData = {};
263
+ }
249
264
  const merged = { ...currentData };
250
265
  if (credential) {
251
266
  merged[provider] = credential;
@@ -327,6 +342,10 @@ export class AuthStorage {
327
342
  throw new Error(`Unknown OAuth provider: ${providerId}`);
328
343
  }
329
344
  const credentials = await provider.login(callbacks);
345
+ // Clear loadError so persistProviderChange doesn't silently skip the write.
346
+ // A successful login produces known-good credentials — safe to persist even
347
+ // if the initial file read failed (corrupt file, lock contention, etc.).
348
+ this.loadError = null;
330
349
  this.set(providerId, { type: 'oauth', ...credentials });
331
350
  }
332
351
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shortcutxl",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/",
Binary file