shortcutxl 0.2.5 → 0.2.6

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 (50) hide show
  1. package/dist/cli/args.js +1 -0
  2. package/dist/custom/agents/installation.js +2 -2
  3. package/dist/custom/preflight.js +128 -81
  4. package/dist/custom/prompts/action.js +1 -2
  5. package/dist/custom/prompts/installation.js +88 -30
  6. package/dist/custom/sync-xll.js +88 -7
  7. package/dist/custom/uninstall.js +140 -0
  8. package/dist/main.js +17 -4
  9. package/package.json +4 -2
  10. package/xll/ShortcutXL.xll +0 -0
  11. package/xll/modules/debug_render.py +272 -272
  12. package/xll/modules/gameboy.py +241 -241
  13. package/xll/modules/pong.py +188 -188
  14. package/xll/modules/shortcut_xl/__init__.py +39 -39
  15. package/xll/modules/shortcut_xl/_com.py +108 -108
  16. package/xll/modules/shortcut_xl/_managed.py +139 -139
  17. package/xll/modules/shortcut_xl/_threading.py +161 -161
  18. package/xll/modules/shortcut_xl/_tracking.py +259 -259
  19. package/xll/modules/shortcut_xl/api/__init__.py +4 -4
  20. package/xll/modules/shortcut_xl/api/categorize.py +202 -202
  21. package/xll/modules/shortcut_xl/api/format.py +250 -250
  22. package/xll/modules/shortcut_xl/api/named_ranges.py +50 -50
  23. package/xll/modules/shortcut_xl/api/picture.py +61 -61
  24. package/xll/modules/shortcut_xl/api/range_formatter.py +418 -418
  25. package/xll/modules/shortcut_xl/api/style.py +93 -93
  26. package/xll/modules/shortcut_xl/api/utils/com_utils.py +47 -47
  27. package/xll/modules/shortcut_xl/api/utils/helpers.py +145 -145
  28. package/xll/modules/shortcut_xl/api/utils/numerical.py +26 -26
  29. package/xll/modules/shortcut_xl/api/utils/ranges.py +93 -93
  30. package/xll/modules/shortcut_xl/api/utils/style_utils.py +28 -28
  31. package/xll/modules/shortcut_xl/api/workbook.py +287 -287
  32. package/xll/modules/shortcut_xl/api/worksheet.py +484 -484
  33. package/xll/modules/shortcut_xl/api-reference.py +188 -188
  34. package/xll/modules/stocks.py +100 -100
  35. package/xll/modules/shortcut_xl/_categorize.py +0 -202
  36. package/xll/modules/shortcut_xl/_com_utils.py +0 -47
  37. package/xll/modules/shortcut_xl/_format.py +0 -250
  38. package/xll/modules/shortcut_xl/_helpers.py +0 -143
  39. package/xll/modules/shortcut_xl/_named_ranges.py +0 -50
  40. package/xll/modules/shortcut_xl/_picture.py +0 -61
  41. package/xll/modules/shortcut_xl/_range_formatter.py +0 -422
  42. package/xll/modules/shortcut_xl/_style.py +0 -93
  43. package/xll/modules/shortcut_xl/_workbook.py +0 -287
  44. package/xll/modules/shortcut_xl/_worksheet.py +0 -474
  45. package/xll/modules/shortcut_xl/utils/__init__.py +0 -5
  46. package/xll/modules/shortcut_xl/utils/numerical.py +0 -26
  47. package/xll/modules/shortcut_xl/utils/ranges.py +0 -93
  48. package/xll/modules/shortcut_xl/utils/style.py +0 -28
  49. package/xll/python3.dll +0 -0
  50. package/xll/python312.dll +0 -0
package/dist/cli/args.js CHANGED
@@ -173,6 +173,7 @@ ${chalk.bold('Commands:')}
173
173
  ${APP_NAME} update [source] Update installed extensions (skips pinned sources)
174
174
  ${APP_NAME} list List installed extensions from settings
175
175
  ${APP_NAME} config Open TUI to enable/disable package resources
176
+ ${APP_NAME} uninstall Remove registry keys, PATH entry, and XLL files
176
177
  ${APP_NAME} <command> --help Show help for install/remove/update/list
177
178
 
178
179
  ${chalk.bold('Options:')}
@@ -1,11 +1,11 @@
1
1
  import { BASH, EDIT, EXCEL_EXEC, GREP, READ, SWITCH_MODE, WRITE } from '../../tool-names.js';
2
2
  import { buildInstallationPrompt } from '../prompts/installation.js';
3
3
  const INSTALLATION_TOOLS = [READ, BASH, EDIT, WRITE, GREP, EXCEL_EXEC, SWITCH_MODE];
4
- export function installationAgent() {
4
+ export function installationAgent(preflightLog) {
5
5
  return {
6
6
  name: 'installation',
7
7
  description: 'First-time setup for ShortcutXL',
8
- systemPrompt: buildInstallationPrompt(),
8
+ systemPrompt: buildInstallationPrompt(preflightLog),
9
9
  tools: INSTALLATION_TOOLS,
10
10
  switchTo: ['action']
11
11
  };
@@ -14,45 +14,46 @@ import { resetShellConfig } from '../utils/shell.js';
14
14
  import { EXCEL_HTTP_URL } from './constants.js';
15
15
  import { fetchExcelConfig } from './excel-config.js';
16
16
  import { getStableXllDir } from './sync-xll.js';
17
- // ── Helpers ──────────────────────────────────────────────────────────────
18
- function log(msg) {
19
- console.log(chalk.cyan(' → ') + msg);
20
- }
21
- function ok(msg) {
22
- console.log(chalk.green(' ') + msg);
23
- }
24
- function warn(msg) {
25
- console.log(chalk.yellow(' ⚠ ') + msg);
17
+ /** Accumulated log entries, exposed to callers via runPreflight(). */
18
+ const preflightLog = [];
19
+ let currentStep = 'setup';
20
+ const LOG_STYLES = {
21
+ info: { icon: '→', color: chalk.cyan },
22
+ ok: { icon: '✓', color: chalk.green },
23
+ warn: { icon: '⚠', color: chalk.yellow }
24
+ };
25
+ function emit(level, msg) {
26
+ const { icon, color } = LOG_STYLES[level];
27
+ console.log(color(` ${icon} `) + msg);
28
+ preflightLog.push({ level, step: currentStep, message: msg });
26
29
  }
30
+ const log = (msg) => emit('info', msg);
31
+ const ok = (msg) => emit('ok', msg);
32
+ const warn = (msg) => emit('warn', msg);
27
33
  function header(step) {
34
+ currentStep = step.toLowerCase().replace(/\s+/g, '-');
28
35
  console.log();
29
36
  console.log(chalk.bold.white(` ${step}`));
30
37
  console.log(chalk.dim(' ' + '─'.repeat(40)));
31
38
  }
32
- /** Prompt user to press Enter to continue. */
33
- function promptEnter(message) {
39
+ /** Prompt the user via readline and return their answer. */
40
+ function prompt(text) {
34
41
  return new Promise((res) => {
35
42
  const rl = createInterface({ input: process.stdin, output: process.stdout });
36
- rl.question(`${chalk.cyan(' →')} ${message} `, () => {
37
- rl.close();
38
- res();
39
- });
43
+ rl.question(text, (answer) => { rl.close(); res(answer); });
40
44
  });
41
45
  }
42
- /** Prompt user for yes/no confirmation. */
43
- function promptConfirm(message) {
44
- return new Promise((res) => {
45
- const rl = createInterface({ input: process.stdin, output: process.stdout });
46
- rl.question(`${chalk.yellow(' ⚠')} ${message} [y/N] `, (answer) => {
47
- rl.close();
48
- res(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
49
- });
50
- });
46
+ function promptEnter(message) {
47
+ return prompt(`${chalk.cyan(' →')} ${message} `).then(() => { });
48
+ }
49
+ async function promptConfirm(message) {
50
+ const answer = await prompt(`${chalk.yellow(' ⚠')} ${message} [y/N] `);
51
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
51
52
  }
52
53
  /** Run a command synchronously and return { ok, stdout, stderr }. */
53
54
  function run(cmd, options) {
54
55
  try {
55
- const result = spawnSync(cmd, {
56
+ const result = spawnSync(cmd, [], {
56
57
  encoding: 'utf-8',
57
58
  timeout: options?.timeout ?? 30_000,
58
59
  shell: true,
@@ -68,6 +69,11 @@ function run(cmd, options) {
68
69
  return { ok: false, stdout: '', stderr: 'command failed' };
69
70
  }
70
71
  }
72
+ /** Run a PowerShell script via base64-encoded command. */
73
+ function runPS(script, options) {
74
+ const encoded = Buffer.from(script, 'utf16le').toString('base64');
75
+ return run(`powershell -NoProfile -EncodedCommand ${encoded}`, options);
76
+ }
71
77
  // ── Progress spinner ────────────────────────────────────────────────────
72
78
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
73
79
  /**
@@ -78,7 +84,7 @@ function runWithProgress(cmd, options) {
78
84
  const label = options?.label ?? 'Working';
79
85
  const timeout = options?.timeout ?? 120_000;
80
86
  return new Promise((resolve) => {
81
- const child = cpSpawn(cmd, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
87
+ const child = cpSpawn(cmd, [], { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
82
88
  let stdout = '';
83
89
  let stderr = '';
84
90
  let lastLine = '';
@@ -164,27 +170,27 @@ async function ensureGitBash() {
164
170
  warn('Installed but not detected yet — try restarting your terminal.');
165
171
  return;
166
172
  }
167
- ok(`Installed.`);
173
+ ok('Installed.');
174
+ }
175
+ /** Detect Python 3.12 via `python` or `py -3.12` launcher. */
176
+ function detectPython312() {
177
+ const py = run('python --version');
178
+ if (py.ok && py.stdout.includes('3.12'))
179
+ return { cmd: 'python', version: py.stdout };
180
+ const launcher = run('py -3.12 --version');
181
+ if (launcher.ok)
182
+ return { cmd: 'py -3.12', version: launcher.stdout };
183
+ return null;
168
184
  }
169
185
  async function ensurePython() {
170
186
  header('Python');
171
- let pythonCmd = null;
172
- // Check for Python 3.12 specifically
173
- const pyCheck = run('python --version');
174
- if (pyCheck.ok && pyCheck.stdout.includes('3.12')) {
175
- ok(`${pyCheck.stdout}`);
176
- pythonCmd = 'python';
187
+ let freshInstall = false;
188
+ let detected = detectPython312();
189
+ if (detected) {
190
+ ok(detected.version);
177
191
  }
178
192
  else {
179
- // Also check py launcher
180
- const pyLauncher = run('py -3.12 --version');
181
- if (pyLauncher.ok) {
182
- ok(`${pyLauncher.stdout}`);
183
- pythonCmd = 'py -3.12';
184
- }
185
- }
186
- if (!pythonCmd) {
187
- const confirmed = await promptConfirm('Install Python and register ShortcutXL with Excel? This is needed for it to control Excel.');
193
+ const confirmed = await promptConfirm('Install Python 3.12? ShortcutXL needs it to control Excel.');
188
194
  if (!confirmed) {
189
195
  warn('Skipped — the setup agent will help.');
190
196
  return;
@@ -195,23 +201,33 @@ async function ensurePython() {
195
201
  warn('Install failed — the setup agent will help.');
196
202
  return;
197
203
  }
198
- // Verify
199
- const verify = run('python --version');
200
- if (verify.ok && verify.stdout.includes('3.12')) {
201
- ok(`${verify.stdout}`);
202
- pythonCmd = 'python';
204
+ freshInstall = true;
205
+ detected = detectPython312();
206
+ if (!detected) {
207
+ warn('Installed but not detected yet — try restarting your terminal.');
208
+ return;
209
+ }
210
+ ok(detected.version);
211
+ }
212
+ const pythonCmd = detected.cmd;
213
+ // The XLL finds Python via the Windows registry, not PATH. Verify the
214
+ // registry key exists — conda/pyenv-win installs may have Python on PATH
215
+ // but no registry entry, which silently breaks the XLL at runtime.
216
+ const regHkcu = run('reg query "HKCU\\SOFTWARE\\Python\\PythonCore\\3.12\\InstallPath" /ve');
217
+ const regResult = regHkcu.ok
218
+ ? regHkcu
219
+ : run('reg query "HKLM\\SOFTWARE\\Python\\PythonCore\\3.12\\InstallPath" /ve');
220
+ if (!regResult.ok) {
221
+ if (freshInstall) {
222
+ warn('Python 3.12 was installed but the registry key is missing — ' +
223
+ 'the setup agent will help resolve this.');
203
224
  }
204
225
  else {
205
- const verifyPy = run('py -3.12 --version');
206
- if (verifyPy.ok) {
207
- ok(`${verifyPy.stdout}`);
208
- pythonCmd = 'py -3.12';
209
- }
210
- else {
211
- warn('Installed but not detected yet — try restarting your terminal.');
212
- return;
213
- }
226
+ warn('Python 3.12 is on PATH but not registered in the Windows registry. ' +
227
+ 'The XLL needs the registry key to find Python. ' +
228
+ 'Try reinstalling Python 3.12 from python.org (the official installer writes the registry key).');
214
229
  }
230
+ return;
215
231
  }
216
232
  // Install pywin32 + openpyxl + playwright + httpx
217
233
  log('Installing required packages...');
@@ -252,18 +268,51 @@ async function ensurePython() {
252
268
  }
253
269
  }
254
270
  }
255
- /** Run a PowerShell script via base64-encoded command. */
256
- function runPS(script, options) {
257
- const encoded = Buffer.from(script, 'utf16le').toString('base64');
258
- return run(`powershell -NoProfile -EncodedCommand ${encoded}`, options);
271
+ /**
272
+ * Ensure the XLL directory is on the user's PATH environment variable.
273
+ *
274
+ * Excel's LoadLibrary does not search the loaded DLL's own directory for
275
+ * dependencies — it searches the application directory (Excel.exe's dir),
276
+ * system directories, and PATH. Without this, python312.dll (which lives
277
+ * next to ShortcutXL.xll) is invisible to the loader, causing a cryptic
278
+ * "file format and extension don't match" dialog.
279
+ *
280
+ * Delay-loading python312.dll is not possible because the Python C API
281
+ * exports data symbols (e.g. PyBool_Type) that MSVC cannot delay-load.
282
+ */
283
+ function ensureXllDirOnPath(xllDir) {
284
+ const result = runPS(`
285
+ $dir = "${xllDir}"
286
+ $current = [Environment]::GetEnvironmentVariable('PATH', 'User')
287
+ $entries = $current -split ';' | Where-Object { $_ -ne '' }
288
+ $found = $entries | Where-Object { $_.TrimEnd('\\') -eq $dir.TrimEnd('\\') }
289
+ if ($found) {
290
+ Write-Output 'ALREADY'
291
+ } else {
292
+ $newPath = $current.TrimEnd(';') + ';' + $dir
293
+ [Environment]::SetEnvironmentVariable('PATH', $newPath, 'User')
294
+ Write-Output 'ADDED'
295
+ }
296
+ `);
297
+ if (result.stdout.includes('ADDED')) {
298
+ ok('Added XLL directory to PATH.');
299
+ }
300
+ else if (result.stdout.includes('ALREADY')) {
301
+ // Already on PATH — nothing to do
302
+ }
303
+ else {
304
+ warn('Could not update PATH — the setup agent will help.');
305
+ }
259
306
  }
260
307
  function ensureXllRegistry() {
261
308
  header('Excel Add-in');
262
309
  const xllPath = resolve(getXllPath()).replace(/\//g, '\\');
310
+ const xllDir = resolve(getXllPath(), '..').replace(/\//g, '\\');
263
311
  if (!existsSync(xllPath)) {
264
- warn(`Add-in file not found — the setup agent will help.`);
312
+ warn('Add-in file not found — the setup agent will help.');
265
313
  return;
266
314
  }
315
+ ensureXllDirOnPath(xllDir);
267
316
  // Find Office version and write the OPEN key so Excel loads the XLL on startup.
268
317
  // Creates the Options key if it doesn't exist yet (fresh Office installs).
269
318
  const result = runPS(`
@@ -319,13 +368,13 @@ function ensureXllRegistry() {
319
368
  ok('Already registered.');
320
369
  }
321
370
  else if (result.stdout.includes('REGISTERED:')) {
322
- ok(`Registered with Excel.`);
371
+ ok('Registered with Excel.');
323
372
  }
324
373
  else if (result.stdout.includes('NO_OFFICE')) {
325
374
  warn('Excel not found — the setup agent will help.');
326
375
  }
327
376
  else {
328
- warn(`Registration failed — the setup agent will help.`);
377
+ warn('Registration failed — the setup agent will help.');
329
378
  if (result.stderr)
330
379
  log(chalk.dim(result.stderr));
331
380
  }
@@ -354,14 +403,15 @@ async function ensureExcelAndXll() {
354
403
  else {
355
404
  await promptEnter('Please open any Excel spreadsheet. Press Enter when done.');
356
405
  }
357
- // Don't use `start excel` — opening Excel without a file causes a spurious
358
- // "file format and extension don't match" dialog on the XLL's first load.
406
+ // Don't use `start excel` — we need the user to open a workbook, not bare Excel.
359
407
  log('Waiting for ShortcutXL to start...');
360
408
  for (let i = 0; i < 10; i++) {
361
409
  await new Promise((r) => setTimeout(r, 1000));
410
+ process.stderr.write(`\r ${chalk.cyan(SPINNER_FRAMES[i % SPINNER_FRAMES.length])} Waiting for ShortcutXL... (${i + 1}s)\x1b[K`);
362
411
  try {
363
412
  const res = await fetch(`${EXCEL_HTTP_URL}/ping`, { signal: AbortSignal.timeout(2000) });
364
413
  if (res.ok) {
414
+ process.stderr.write('\r\x1b[K');
365
415
  ok('ShortcutXL is ready.');
366
416
  await verifyXllConfig();
367
417
  return;
@@ -371,6 +421,7 @@ async function ensureExcelAndXll() {
371
421
  // Keep waiting
372
422
  }
373
423
  }
424
+ process.stderr.write('\r\x1b[K');
374
425
  warn('ShortcutXL did not start — the setup agent will help.');
375
426
  }
376
427
  async function verifyXllConfig() {
@@ -382,7 +433,7 @@ async function verifyXllConfig() {
382
433
  }
383
434
  const actualModules = resolve(config.modulesPath);
384
435
  if (actualModules !== expectedModulesDir) {
385
- warn(`Add-in config mismatch — the setup agent will help.`);
436
+ warn('Add-in config mismatch — the setup agent will help.');
386
437
  return;
387
438
  }
388
439
  ok('Add-in config verified.');
@@ -408,7 +459,7 @@ async function smokeTest() {
408
459
  warn(`Connection failed: ${data.error}`);
409
460
  return false;
410
461
  }
411
- ok(`${data.output?.trim()}`);
462
+ ok(data.output?.trim() ?? 'Connected');
412
463
  return true;
413
464
  }
414
465
  catch (e) {
@@ -416,14 +467,14 @@ async function smokeTest() {
416
467
  return false;
417
468
  }
418
469
  }
419
- // ── Main entry point ─────────────────────────────────────────────────────
420
470
  /**
421
471
  * Run deterministic pre-flight checks and auto-install prerequisites.
422
472
  * Best-effort — installs what it can, the installation agent handles the rest.
423
- *
424
- * Returns true if the smoke test passed (setup fully complete).
425
473
  */
426
474
  export async function runPreflight() {
475
+ // Reset state for this run
476
+ preflightLog.length = 0;
477
+ currentStep = 'setup';
427
478
  console.log();
428
479
  console.log(chalk.bold.white(' Setting up ShortcutXL...'));
429
480
  console.log(chalk.dim(' ' + '─'.repeat(40)));
@@ -439,16 +490,12 @@ export async function runPreflight() {
439
490
  await ensureExcelAndXll();
440
491
  // Smoke test — if this passes, everything works
441
492
  const passed = await smokeTest();
442
- if (passed) {
443
- console.log();
444
- ok('Preflight passed! The installation agent will verify everything.');
445
- console.log();
446
- }
447
- else {
448
- console.log();
449
- warn('Some steps need attention — the setup agent will help.');
450
- console.log();
451
- }
452
- return passed;
493
+ currentStep = 'summary';
494
+ console.log();
495
+ passed
496
+ ? ok('Preflight complete.')
497
+ : warn('Some steps need attention — the setup agent will help.');
498
+ console.log();
499
+ return { passed, log: [...preflightLog] };
453
500
  }
454
501
  //# sourceMappingURL=preflight.js.map
@@ -38,8 +38,7 @@ bash: use it for everything else, including python
38
38
 
39
39
  Attachments, Read-Only Operations
40
40
  - The general principle is to avoid cluttering the user's Excel with files they don't need to see or edit
41
- - Use openpyxl / xlrd for simple reading operations
42
- - Use COM for more complex reading operations, but given that it displays on the user's desktop, read one file at a time
41
+ - Use COM for reading operations, but given that it displays on the user's desktop, read one file at a time
43
42
 
44
43
  PDF Processing:
45
44
  - You MUST ALWAYS use document readers to understand and extract data from PDFs, no matter if it is programmatic extraction or multimodal extraction.
@@ -1,25 +1,60 @@
1
1
  /**
2
2
  * Installation system prompt.
3
3
  *
4
- * All steps are always included so the agent has full context.
4
+ * Includes the preflight source code so the agent understands exactly what
5
+ * was already attempted, plus a structured log of preflight results.
5
6
  * The agent creates the installed marker once setup is verified,
6
7
  * so this prompt won't show on subsequent launches.
7
8
  */
9
+ import { readFileSync } from 'fs';
8
10
  import { join, resolve } from 'path';
11
+ import { fileURLToPath } from 'url';
9
12
  import { getAgentDir, getInstalledMarkerPath } from '../../config.js';
10
13
  import { EXCEL_HTTP_URL } from '../constants.js';
11
14
  import { getStableXllDir } from '../sync-xll.js';
12
- export function buildInstallationPrompt() {
15
+ /**
16
+ * Read the preflight source to embed in the prompt.
17
+ * In dev we read the .ts file; in production (dist/) we read the compiled .js.
18
+ * Both live next to this file's parent directory as ../preflight.{ts,js}.
19
+ */
20
+ function getPreflightSource() {
21
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
22
+ const base = join(thisDir, '..', 'preflight');
23
+ for (const ext of ['.ts', '.js']) {
24
+ try {
25
+ return readFileSync(base + ext, 'utf-8');
26
+ }
27
+ catch {
28
+ // try next
29
+ }
30
+ }
31
+ return '(preflight source not found)';
32
+ }
33
+ /** Format the preflight log for the system prompt. */
34
+ function formatPreflightLog(log) {
35
+ if (log.length === 0)
36
+ return '(no preflight log available)';
37
+ const icons = { info: '[INFO]', ok: '[ OK ]', warn: '[WARN]' };
38
+ return log.map((e) => `${icons[e.level]} [${e.step}] ${e.message}`).join('\n');
39
+ }
40
+ export function buildInstallationPrompt(preflightLog) {
13
41
  const stableXllDir = resolve(getStableXllDir());
14
42
  const modulesDir = join(stableXllDir, 'modules');
15
43
  const xllPath = join(stableXllDir, 'ShortcutXL.xll');
16
44
  const agentDir = getAgentDir();
17
45
  const installedMarker = getInstalledMarkerPath();
46
+ const preflightSource = getPreflightSource();
47
+ const preflightOutput = preflightLog
48
+ ? formatPreflightLog(preflightLog)
49
+ : '(preflight did not run)';
18
50
  return `\
19
- You are guiding a first-time user through ShortcutXL setup. Walk them through each step in order.
20
- ALWAYS ask before starting every step for user permission, you are installing / opening things on their computer.
21
- Be conversational — confirm each step succeeds before moving on.
22
- If something fails, troubleshoot with the user before continuing. Skip steps that are already complete.
51
+ You are guiding a first-time user through ShortcutXL setup. A deterministic preflight script already ran before you — it attempted to install prerequisites and verify the setup. Review the preflight results below to understand what succeeded and what needs fixing.
52
+
53
+ **General rules:**
54
+ - Check what's already in place before doing anything. If a step passed in preflight, verify it quickly and move on.
55
+ - Focus your effort on steps that preflight flagged as warnings or failures.
56
+ - ALWAYS ask for user permission before installing anything. If something fails, troubleshoot with the user before continuing.
57
+ - Be conversational — confirm each step succeeds before moving on.
23
58
 
24
59
  ## Background
25
60
 
@@ -31,6 +66,7 @@ ShortcutXL is a two-part system:
31
66
  ## Where things live
32
67
 
33
68
  - **XLL binaries**: ${stableXllDir} — Automatically synced from the npm package on every startup. The registry key MUST point to this stable path, not the npm package directory — that path changes on every update.
69
+ - **Python DLLs**: python3.dll and python312.dll in ${stableXllDir}. These come from the user's local Python 3.12 install and are copied at startup. If missing, Python 3.12 is either not installed or not registered in the Windows registry.
34
70
  - **User modules**: ${modulesDir} — Where shortcut_xl.py (the core helper library) and user-created UDF modules live. The XLL hot-reloads .py files from this directory.
35
71
  - **Agent config**: ${agentDir} — Settings, auth tokens, session history.
36
72
 
@@ -38,26 +74,28 @@ ShortcutXL is a two-part system:
38
74
 
39
75
  ## Step 0: Verify Shell
40
76
 
41
- You need Git Bash to be installed. Check to see if it is by running some simple commands, like echo ok && git --version.
42
-
43
- If Git Bash is not installed, you MUST
44
- - Ask the user for permission to install Git for Windows.
45
- - temporarily run cmd or powershell to execute commands. Do NOT use bash syntax. Paths with spaces must be quoted carefully — cmd.exe strips the first and last quote when there are multiple pairs. Prefer short commands like \`git --version\` or \`where bash\`.
77
+ Check that Git Bash is working: \`echo ok && git --version\`
46
78
 
47
- Bash installation instructions:
48
- - First check if winget is available. If winget is missing, install it.
49
- - Then install git, accepting source and package agreements. Tell the user to expect a Windows UAC prompt — they must click Yes. Verify Git installed.
50
- - Verify bash is now available. If it succeeds, the shell has automatically switched from cmd.exe to Git Bash. All subsequent commands will use bash.
51
- - sh is installed. Run a simple bash command to confirm bash works. If it fails, the error output will tell the user what to install.
79
+ If Git Bash is not installed:
80
+ - You MUST ask the user for permission first.
81
+ - Use cmd or powershell temporarily (not bash syntax). Paths with spaces must be quoted carefully.
82
+ - Install via: \`winget install --id Git.Git -e -h --accept-source-agreements --accept-package-agreements\`
83
+ - Tell the user to expect a UAC prompt they must click Yes.
84
+ - Verify afterwards: \`git --version\` and \`bash --version\`
52
85
 
53
86
  ---
54
87
 
55
- ## Step 1: Install Python 3.12
88
+ ## Step 1: Verify Python 3.12
56
89
 
57
90
  The XLL embeds a Python interpreter and finds it via the Windows registry. Without Python 3.12 + pywin32, the XLL loads but Python execution fails silently.
58
91
 
59
- - Check if Python 3.12 is installed. If missing, ask the user for permission, then download Python 3.12 and run a silent per-user install. Verify that it is installed correctly after
60
- - Install pywin32 and openpyxl
92
+ - **Check registry**: \`reg query "HKCU\\SOFTWARE\\Python\\PythonCore\\3.12\\InstallPath" //ve\` (or the HKLM equivalent). The XLL finds Python via the registry, NOT the PATH conda/pyenv-win installs often have Python on PATH but no registry entry, which silently breaks the XLL. If the key is missing, install (or reinstall) Python 3.12 from python.org (the official installer writes the registry key).
93
+ - **Check pip packages**: \`python -m pip show pywin32 openpyxl\`. Install any that are missing.
94
+ - **Check Python DLLs**: \`ls ${stableXllDir.replace(/\\/g, '/')}/python3.dll ${stableXllDir.replace(/\\/g, '/')}/python312.dll\`. These are copied automatically from the user's Python 3.12 install at startup. If missing and the registry key exists, copy them manually:
95
+ \`\`\`bash
96
+ cp "<python-install-dir>/python3.dll" "${stableXllDir.replace(/\\/g, '/')}/"
97
+ cp "<python-install-dir>/python312.dll" "${stableXllDir.replace(/\\/g, '/')}/"
98
+ \`\`\`
61
99
 
62
100
  ---
63
101
 
@@ -74,26 +112,26 @@ All of this while keeping data local — workbooks, files, and skills stay on th
74
112
  **How to register (technical details for you, the agent):**
75
113
  Excel checks HKCU\\Software\\Microsoft\\Office\\<version>\\Excel\\Options for keys named OPEN, OPEN1, OPEN2, etc. Each value is a path to an add-in to load at startup.
76
114
 
77
- - Check to see if the registry already has ShortcutXL.
115
+ Check to see if the registry already has ShortcutXL. If it is not registered:
78
116
  - Ask the user for permission — explain you're adding a small user-level setting (no admin needed) so Excel knows to load ShortcutXL on startup.
79
117
  - Detect the Office version, find the next available OPEN slot, and write the key with value /R "${xllPath}".
80
- - If Excel is already running, ask the user to save their work first, then close and reopen it. Do NOT proceed to Step 3 until Excel has been restarted.
81
- - If Excel is not running, launch it and wait a few seconds for it to start up.
82
118
 
83
119
  ---
84
120
 
85
121
  ## Step 3: Verify XLL connection
86
122
 
87
- - To verify that the XLL is hooked into Excel, we have to restart Excel.
88
- - It is CRITICAL that you do not open Excel yourself at this step (Excel will attempt to open shortcutXL.xll and we get a suspicious splash screen). You MUST ask the user to open Excel themselves.
89
- - If Excel is already running, ask the user to save their work first, then close and reopen it.
90
- - If Excel is not running, ask the user to open it.
91
-
92
- Once you receive confirmation that Excel is open, it loads the XLL which initializes Python and starts the HTTP server on port 8080.
123
+ Check that Excel is open and it loads the XLL which initializes Python and starts the HTTP server on port 8080.
93
124
  - Check: \`curl ${EXCEL_HTTP_URL}/config\`. Expected: JSON with xll_dir and modules_path. Retry one time if Excel is still starting up.
94
125
  - Verify the paths match: The config response's \`modules_path\` MUST be \`${modulesDir}\`. If it points elsewhere, the registry key is loading an old or wrong copy of the XLL — fix the registry to point to \`${xllPath}\` and restart Excel.
95
126
  - If it fails: check if Excel is open, check File > Options > Add-ins for ShortcutXL, check %TEMP%\\shortcutxl.log for errors.
96
127
 
128
+ If this does not work upon initial checks, then Excel needs to be restarted for the XLLs to be loaded.
129
+ - It is CRITICAL that you do not open Excel yourself during installation (Excel will attempt to open shortcutXL.xll and we get a suspicious splash screen). You MUST ask the user to open Excel themselves.
130
+ - If Excel is already running, ask the user to save their work first, then close and reopen it.
131
+ - If Excel is not running, ask the user to open it.
132
+
133
+ Once done, re-verify connection and troubleshoot further if it is not verified. ALWAYS ask the user to open Excel themselves.
134
+
97
135
  ---
98
136
 
99
137
  ## Step 4: Smoke Test
@@ -109,7 +147,7 @@ If this returns the Excel version, setup is complete. Create the marker file \`$
109
147
 
110
148
  Tell the user what to expect going forward:
111
149
  - **From now on, just run \`shortcut\`** — Excel automatically opens with the agent loaded if it's not already running. No manual setup needed again.
112
- - Tell the user that they can either ask to do things in Excel right away — rading, writing, sensitivity tables, multi-workbook workflows, etc... AND / OR
150
+ - Tell the user that they can either ask to do things in Excel right away — reading, writing, sensitivity tables, multi-workbook workflows, etc... AND / OR
113
151
  - Explore capabilities and extensibility — the user can ask the agent to build custom tools, connect to APIs (e.g. internal databases, Daloopa, Bloomberg, etc.), and add integrations. The agent can modify itself to add new capabilities on demand.
114
152
  - Ask what they'd like to do first!
115
153
 
@@ -124,6 +162,26 @@ Tell the user what to expect going forward:
124
162
 
125
163
  ## Troubleshooting
126
164
 
127
- - **ShortcutXL.xll opens as a file** — If the user sees a warning dialog about the file format or ShortcutXL.xll being opened as a spreadsheet, tell them to close that dialog and close Excel entirely. Then ask them to open any Excel file themselves — this one time only, to complete setup. Once they have a spreadsheet open, retry the verification.`;
165
+ - **ShortcutXL.xll opens as a file** — If the user sees a warning dialog about the file format or ShortcutXL.xll being opened as a spreadsheet, tell them to close that dialog and close Excel entirely. Then ask them to open any Excel file themselves — this one time only, to complete setup. Once they have a spreadsheet open, retry the verification.
166
+
167
+ ---
168
+
169
+ ## Preflight Source Code
170
+
171
+ The following is the exact preflight code that ran before you started. Use it to understand what was already attempted, what commands were run, and what each step does. This is your reference for troubleshooting — if a step failed, you can see exactly what command was tried and adapt.
172
+
173
+ \`\`\`typescript
174
+ ${preflightSource}
175
+ \`\`\`
176
+
177
+ ---
178
+
179
+ ## Preflight Results
180
+
181
+ The preflight script ran the following steps. Lines marked [WARN] need your attention. Lines marked [ OK ] can be verified quickly and moved past.
182
+
183
+ \`\`\`
184
+ ${preflightOutput}
185
+ \`\`\``;
128
186
  }
129
187
  //# sourceMappingURL=installation.js.map