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.
- package/dist/cli/args.js +1 -0
- package/dist/custom/agents/installation.js +2 -2
- package/dist/custom/preflight.js +128 -81
- package/dist/custom/prompts/action.js +1 -2
- package/dist/custom/prompts/installation.js +88 -30
- package/dist/custom/sync-xll.js +88 -7
- package/dist/custom/uninstall.js +140 -0
- package/dist/main.js +17 -4
- package/package.json +4 -2
- package/xll/ShortcutXL.xll +0 -0
- package/xll/modules/debug_render.py +272 -272
- package/xll/modules/gameboy.py +241 -241
- package/xll/modules/pong.py +188 -188
- package/xll/modules/shortcut_xl/__init__.py +39 -39
- package/xll/modules/shortcut_xl/_com.py +108 -108
- package/xll/modules/shortcut_xl/_managed.py +139 -139
- package/xll/modules/shortcut_xl/_threading.py +161 -161
- package/xll/modules/shortcut_xl/_tracking.py +259 -259
- package/xll/modules/shortcut_xl/api/__init__.py +4 -4
- package/xll/modules/shortcut_xl/api/categorize.py +202 -202
- package/xll/modules/shortcut_xl/api/format.py +250 -250
- package/xll/modules/shortcut_xl/api/named_ranges.py +50 -50
- package/xll/modules/shortcut_xl/api/picture.py +61 -61
- package/xll/modules/shortcut_xl/api/range_formatter.py +418 -418
- package/xll/modules/shortcut_xl/api/style.py +93 -93
- package/xll/modules/shortcut_xl/api/utils/com_utils.py +47 -47
- package/xll/modules/shortcut_xl/api/utils/helpers.py +145 -145
- package/xll/modules/shortcut_xl/api/utils/numerical.py +26 -26
- package/xll/modules/shortcut_xl/api/utils/ranges.py +93 -93
- package/xll/modules/shortcut_xl/api/utils/style_utils.py +28 -28
- package/xll/modules/shortcut_xl/api/workbook.py +287 -287
- package/xll/modules/shortcut_xl/api/worksheet.py +484 -484
- package/xll/modules/shortcut_xl/api-reference.py +188 -188
- package/xll/modules/stocks.py +100 -100
- package/xll/modules/shortcut_xl/_categorize.py +0 -202
- package/xll/modules/shortcut_xl/_com_utils.py +0 -47
- package/xll/modules/shortcut_xl/_format.py +0 -250
- package/xll/modules/shortcut_xl/_helpers.py +0 -143
- package/xll/modules/shortcut_xl/_named_ranges.py +0 -50
- package/xll/modules/shortcut_xl/_picture.py +0 -61
- package/xll/modules/shortcut_xl/_range_formatter.py +0 -422
- package/xll/modules/shortcut_xl/_style.py +0 -93
- package/xll/modules/shortcut_xl/_workbook.py +0 -287
- package/xll/modules/shortcut_xl/_worksheet.py +0 -474
- package/xll/modules/shortcut_xl/utils/__init__.py +0 -5
- package/xll/modules/shortcut_xl/utils/numerical.py +0 -26
- package/xll/modules/shortcut_xl/utils/ranges.py +0 -93
- package/xll/modules/shortcut_xl/utils/style.py +0 -28
- package/xll/python3.dll +0 -0
- 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
|
};
|
package/dist/custom/preflight.js
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
33
|
-
function
|
|
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(
|
|
37
|
-
rl.close();
|
|
38
|
-
res();
|
|
39
|
-
});
|
|
43
|
+
rl.question(text, (answer) => { rl.close(); res(answer); });
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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(
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
/**
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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` —
|
|
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(
|
|
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(
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
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:
|
|
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
|
|
60
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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 —
|
|
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
|