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 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
- const WORK_DIR = path.join(process.cwd(), '.preview', jobId);
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
- // Log with prefix to indicate we are handling the Env quirk
34
- await ctx.actions.runAction('core:log', { level: 'info', message: '[WSL] Copying extension to Windows Temp: C:\\ai-ext-preview' });
35
- // In a real scenario we might need to copy to a Windows-accessible path if /home/... isn't mapped well,
36
- // but usually \\wsl$\... works or user is mapped.
37
- // For now assuming direct path works or user has mapping.
38
- // Actually, verify path.
39
- // IMPORTANT: In WSL, standard linux paths might not be readable by Windows Chrome directly
40
- // without `\\wsl$\...` mapping.
41
- // However, previous logs showed "Failed to load... CDP connection closed" which means
42
- // Chrome DID try to load it but failed communication.
43
- // So path is likely fine.
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 subprocess = spawn(chromePath, [
47
- `--load-extension=${DIST_DIR}`,
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
- 'https://google.com'
50
- ], {
51
- detached: true,
52
- stdio: 'ignore'
53
- });
54
- subprocess.unref();
55
- return true;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-extension-preview",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local preview tool for AI Extension Builder",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,4 +49,4 @@
49
49
  "tsx": "^4.21.0",
50
50
  "typescript": "^5.7.2"
51
51
  }
52
- }
52
+ }