ai-extension-preview 0.1.0 → 0.1.2
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/index.js +9 -2
- package/dist/plugins/BrowserPlugin.js +119 -21
- package/dist/plugins/DownloaderPlugin.js +5 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Command } from 'commander';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
|
+
import os from 'os';
|
|
7
8
|
import { Runtime } from 'skeleton-crew-runtime';
|
|
8
9
|
import { CorePlugin } from './plugins/CorePlugin.js';
|
|
9
10
|
import { DownloaderPlugin } from './plugins/DownloaderPlugin.js';
|
|
@@ -77,7 +78,10 @@ async function main() {
|
|
|
77
78
|
userId = authData.userId || userId;
|
|
78
79
|
token = authData.token || token;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
// Use os.homedir() to ensure we have write permissions
|
|
82
|
+
// Git Bash sometimes defaults cwd to C:\Program Files\Git which causes EPERM
|
|
83
|
+
const HOME_DIR = os.homedir();
|
|
84
|
+
const WORK_DIR = path.join(HOME_DIR, '.ai-extension-preview', jobId);
|
|
81
85
|
// 1. Initialize Runtime
|
|
82
86
|
const runtime = new Runtime({
|
|
83
87
|
hostContext: {
|
|
@@ -133,4 +137,7 @@ process.on('uncaughtException', (err) => {
|
|
|
133
137
|
process.on('unhandledRejection', (reason) => {
|
|
134
138
|
console.error('Unhandled Rejection:', reason);
|
|
135
139
|
});
|
|
136
|
-
main()
|
|
140
|
+
main().catch(err => {
|
|
141
|
+
console.error(chalk.red('Fatal Error:'), err.message || err);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import webExt from 'web-ext';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
-
import fs from 'fs';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
5
|
const CHROME_PATHS = [
|
|
6
|
+
// Standard Windows Paths
|
|
7
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
8
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
9
|
+
// WSL Mappings
|
|
6
10
|
'/mnt/c/Program Files/Google/Chrome/Application/chrome.exe',
|
|
7
11
|
'/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
12
|
+
// Git Bash / Unix-y Windows Environment Mappings
|
|
8
13
|
'/c/Program Files/Google/Chrome/Application/chrome.exe',
|
|
9
14
|
'/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
15
|
+
// Linux
|
|
10
16
|
'/usr/bin/google-chrome',
|
|
11
17
|
'/usr/bin/chromium'
|
|
12
18
|
];
|
|
@@ -30,33 +36,125 @@ export const BrowserPlugin = {
|
|
|
30
36
|
await ctx.actions.runAction('core:log', { level: 'error', message: 'Chrome not found for detached launch.' });
|
|
31
37
|
return false;
|
|
32
38
|
}
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
// WSL Detection & Handling
|
|
40
|
+
let extensionPath = DIST_DIR;
|
|
41
|
+
const isWSL = fs.existsSync('/mnt/c'); // Simple check for WSL
|
|
42
|
+
if (isWSL) {
|
|
43
|
+
try {
|
|
44
|
+
const WIN_TEMP_DIR = '/mnt/c/Temp/ai-ext-preview';
|
|
45
|
+
const WIN_PATH_FOR_CHROME = 'C:/Temp/ai-ext-preview';
|
|
46
|
+
// Pre-flight check: Validating WSL Interop
|
|
47
|
+
// We try to run cmd.exe simply to check if the OS allows it.
|
|
48
|
+
try {
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
const check = spawn('cmd.exe', ['/c', 'ver'], { stdio: 'ignore' });
|
|
51
|
+
check.on('error', reject);
|
|
52
|
+
check.on('close', (code) => {
|
|
53
|
+
if (code === 0)
|
|
54
|
+
resolve(true);
|
|
55
|
+
else
|
|
56
|
+
reject(new Error(`Exit code ${code}`));
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (interopErr) {
|
|
61
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `[FATAL] WSL Interop is broken on this system.` });
|
|
62
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Linux cannot launch Windows applications (cmd.exe failed).` });
|
|
63
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `PLEASE FIX: Open PowerShell as Admin and run 'wsl --shutdown', then restart.` });
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `[WSL] Copying extension to Windows Temp: ${WIN_PATH_FOR_CHROME}` });
|
|
67
|
+
// Ensure Windows temp dir exists and is clean
|
|
68
|
+
if (fs.existsSync(WIN_TEMP_DIR)) {
|
|
69
|
+
fs.removeSync(WIN_TEMP_DIR);
|
|
70
|
+
}
|
|
71
|
+
fs.ensureDirSync(WIN_TEMP_DIR);
|
|
72
|
+
// Copy dist content
|
|
73
|
+
fs.copySync(DIST_DIR, WIN_TEMP_DIR);
|
|
74
|
+
extensionPath = WIN_PATH_FOR_CHROME;
|
|
75
|
+
}
|
|
76
|
+
catch (copyErr) {
|
|
77
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to copy to Windows Temp: ${copyErr.message}` });
|
|
78
|
+
// Fallback to original path (might fail if not mapped)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
44
81
|
await ctx.actions.runAction('core:log', { level: 'warning', message: 'Switching to Detached Mode (WSL/GitBash detected).' });
|
|
45
82
|
await ctx.actions.runAction('core:log', { level: 'info', message: 'Browser polling/logging is disabled. Please reload manually on updates.' });
|
|
46
|
-
const
|
|
47
|
-
|
|
83
|
+
const userDataDir = 'C:/Temp/ai-ext-profile';
|
|
84
|
+
// Convert Chrome path to Windows format if in WSL
|
|
85
|
+
// /mnt/c/Program Files/... -> C:\Program Files\...
|
|
86
|
+
let executable = chromePath;
|
|
87
|
+
let args = [
|
|
88
|
+
`--load-extension=${extensionPath}`,
|
|
89
|
+
`--user-data-dir=${userDataDir}`,
|
|
90
|
+
'--no-first-run',
|
|
91
|
+
'--no-default-browser-check',
|
|
48
92
|
'--disable-gpu',
|
|
49
|
-
'
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
'about:blank'
|
|
94
|
+
];
|
|
95
|
+
// If WSL, use a batch file to handle the launch robustly
|
|
96
|
+
if (isWSL) {
|
|
97
|
+
const driveLetter = chromePath.match(/\/mnt\/([a-z])\//)?.[1] || 'c';
|
|
98
|
+
const winChromePath = chromePath
|
|
99
|
+
.replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\`)
|
|
100
|
+
.replace(/\//g, '\\');
|
|
101
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `WSL: Creating launch script...` });
|
|
102
|
+
// Use backslashes for Windows paths in the batch file
|
|
103
|
+
const winDist = 'C:\\Temp\\ai-ext-preview';
|
|
104
|
+
const winProfile = 'C:\\Temp\\ai-ext-profile';
|
|
105
|
+
// Create the batch file content
|
|
106
|
+
// NOTE: We quote the executable path and arguments carefully
|
|
107
|
+
// start "" "Executable" args...
|
|
108
|
+
const batContent = `@echo off
|
|
109
|
+
start "" "${winChromePath}" --load-extension="${winDist}" --user-data-dir="${winProfile}" --no-first-run --no-default-browser-check --disable-gpu about:blank
|
|
110
|
+
exit
|
|
111
|
+
`;
|
|
112
|
+
const batPath = '/mnt/c/Temp/ai-ext-preview/launch.bat';
|
|
113
|
+
const winBatPath = 'C:\\Temp\\ai-ext-preview\\launch.bat';
|
|
114
|
+
try {
|
|
115
|
+
fs.writeFileSync(batPath, batContent);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
await ctx.actions.runAction('core:log', { level: 'error', message: `Failed to write batch file: ${e.message}` });
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `EXEC: ${winBatPath}` });
|
|
122
|
+
// Execute the batch file via cmd.exe using spawn + PATH lookup
|
|
123
|
+
// We rely on 'cmd.exe' being in the path so spawn works without /bin/sh
|
|
124
|
+
const executable = 'cmd.exe';
|
|
125
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN (WSL): ${executable} /c ${winBatPath}` });
|
|
126
|
+
const subprocess = spawn(executable, ['/c', winBatPath], {
|
|
127
|
+
detached: true,
|
|
128
|
+
stdio: 'ignore',
|
|
129
|
+
cwd: '/mnt/c'
|
|
130
|
+
});
|
|
131
|
+
subprocess.unref();
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Standard Windows / Linux Launch
|
|
136
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `SPAWN: ${chromePath}` });
|
|
137
|
+
// On Windows, ensure we quote paths correctly if they have spaces?
|
|
138
|
+
// Node's spawn handles argument quoting automatically usually.
|
|
139
|
+
// But let's log them to be sure
|
|
140
|
+
await ctx.actions.runAction('core:log', { level: 'info', message: `ARGS: ${args.join(' ')}` });
|
|
141
|
+
const subprocess = spawn(executable, args, {
|
|
142
|
+
detached: true,
|
|
143
|
+
stdio: 'ignore'
|
|
144
|
+
});
|
|
145
|
+
subprocess.unref();
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
56
148
|
};
|
|
57
149
|
ctx.actions.registerAction({
|
|
58
150
|
id: 'browser:start',
|
|
59
151
|
handler: async () => {
|
|
152
|
+
// On Windows (including Git Bash), web-ext is unreliable for loading extensions correctly.
|
|
153
|
+
// We force detached mode to ensure the extension loads.
|
|
154
|
+
if (process.platform === 'win32') {
|
|
155
|
+
await ctx.actions.runAction('core:log', { level: 'warning', message: 'Windows detected: Forcing Detached Mode for reliability.' });
|
|
156
|
+
return await launchDetached();
|
|
157
|
+
}
|
|
60
158
|
await ctx.actions.runAction('core:log', { level: 'info', message: 'Launching browser...' });
|
|
61
159
|
try {
|
|
62
160
|
// Try web-ext first
|
|
@@ -41,7 +41,11 @@ export const DownloaderPlugin = {
|
|
|
41
41
|
rejectUnauthorized: false
|
|
42
42
|
})
|
|
43
43
|
});
|
|
44
|
+
const VERSION_FILE = path.join(config.workDir, 'version');
|
|
44
45
|
let lastModified = '';
|
|
46
|
+
if (fs.existsSync(VERSION_FILE)) {
|
|
47
|
+
lastModified = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
|
|
48
|
+
}
|
|
45
49
|
let isChecking = false;
|
|
46
50
|
// Action: Check Status
|
|
47
51
|
ctx.actions.registerAction({
|
|
@@ -64,6 +68,7 @@ export const DownloaderPlugin = {
|
|
|
64
68
|
const success = await ctx.actions.runAction('downloader:download', null);
|
|
65
69
|
if (success) {
|
|
66
70
|
lastModified = newVersion;
|
|
71
|
+
fs.writeFileSync(VERSION_FILE, newVersion);
|
|
67
72
|
ctx.events.emit('downloader:updated', { version: job.version });
|
|
68
73
|
}
|
|
69
74
|
}
|
package/package.json
CHANGED