ai-extension-preview 0.1.9 → 0.1.10

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
@@ -41,7 +41,7 @@ async function authenticate(host) {
41
41
  const statusRes = await axios.get(`${host}/preview/status/${sessionId}`);
42
42
  const data = statusRes.data;
43
43
  if (data.status === 'linked') {
44
- console.log(chalk.green('✔ Connected!'));
44
+ // console.log(chalk.green('✔ Connected!'));
45
45
  if (!data.jobId) {
46
46
  console.error('Error: No Job ID associated with this connection.');
47
47
  process.exit(1);
@@ -97,7 +97,10 @@ async function main() {
97
97
  });
98
98
  // 2. Register Plugins
99
99
  // Note: In a real dynamic system we might load these from a folder
100
- runtime.logger.info('Registering plugins...');
100
+ // console.log('Registering plugins...');
101
+ // Register Plugins
102
+ // UI Plugin first or last?
103
+ // If first, it captures subsequent logs.
101
104
  runtime.registerPlugin(CorePlugin);
102
105
  runtime.registerPlugin(DownloaderPlugin);
103
106
  runtime.registerPlugin(BrowserPlugin);
@@ -111,12 +111,6 @@ export const BrowserPlugin = {
111
111
  fs.ensureDirSync(STAGING_DIR);
112
112
  fs.copySync(DIST_DIR, STAGING_DIR);
113
113
  await ctx.actions.runAction('core:log', { level: 'info', message: `Synced code to Staging` });
114
- // DEBUG: Log contents of staging
115
- try {
116
- const files = fs.readdirSync(STAGING_DIR);
117
- await ctx.actions.runAction('core:log', { level: 'info', message: `Staging Contents: ${files.join(', ')}` });
118
- }
119
- catch (e) { }
120
114
  // Emit staged event for ServerPlugin (optional for now, but good practice)
121
115
  ctx.events.emit('browser:staged', { path: STAGING_DIR });
122
116
  }
@@ -159,48 +153,22 @@ export const BrowserPlugin = {
159
153
  // 1. Use Windows User Profile for staging to avoid Permission/Path issues
160
154
  // 2. Use PowerShell script to launch Chrome to reliably pass arguments
161
155
  // -------------------------------------------------------------------------
162
- // 1. Get Windows User Profile Path
163
- let userProfileWin = '';
164
- try {
165
- // Use async exec to avoid blocking
166
- const { exec } = await import('child_process');
167
- const util = await import('util');
168
- const execAsync = util.promisify(exec);
169
- const { stdout } = await execAsync('cmd.exe /c echo %USERPROFILE%', { encoding: 'utf8' });
170
- userProfileWin = stdout.trim();
171
- }
172
- catch (e) {
173
- await ctx.actions.runAction('core:log', { level: 'error', message: 'Failed to detect Windows User Profile. Defaulting to C:\\Temp' });
174
- userProfileWin = 'C:\\Temp';
175
- }
176
- const stagingDirName = '.ai-extension-preview';
177
- const stagingDirWin = path.posix.join(userProfileWin.replace(/\\/g, '/'), stagingDirName).replace(/\//g, '\\');
178
- // Map Win Path -> WSL Path for copying
179
- const driveMatch = userProfileWin.match(/^([a-zA-Z]):/);
180
- const driveLetter = driveMatch ? driveMatch[1].toLowerCase() : 'c';
181
- const userProfileRoute = userProfileWin.substring(3).replace(/\\/g, '/'); // Users/Name
182
- const wslStagingBase = `/mnt/${driveLetter}/${userProfileRoute}`;
183
- const wslStagingDir = path.posix.join(wslStagingBase, stagingDirName);
184
- try {
185
- if (await fs.pathExists(wslStagingDir))
186
- await fs.remove(wslStagingDir);
187
- // Use async copy to prevent blocking event loop (Fixes 25s lag)
188
- await fs.copy(STAGING_DIR, wslStagingDir);
189
- }
190
- catch (copyErr) {
191
- await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Staging Copy Failed: ${copyErr.message}` });
192
- }
156
+ // 1. Setup Safe Paths (C:\Temp)
157
+ // We use the same path that syncToStaging() used (/mnt/c/Temp/ai-ext-preview)
158
+ const winStagingDir = 'C:\\Temp\\ai-ext-preview';
159
+ const winProfile = 'C:\\Temp\\ai-ext-profile';
160
+ let userProfileWin = 'C:\\Temp'; // Legacy variable support
161
+ const driveLetter = 'c';
193
162
  // Calculate final paths
194
- let finalWinExtensionPath = stagingDirWin;
163
+ let finalWinExtensionPath = winStagingDir;
195
164
  // Handle nested extension root
196
165
  if (extensionRoot !== STAGING_DIR) {
197
166
  const relative = path.relative(STAGING_DIR, extensionRoot);
198
- finalWinExtensionPath = path.posix.join(stagingDirWin.replace(/\\/g, '/'), relative).replace(/\//g, '\\');
167
+ finalWinExtensionPath = path.posix.join(winStagingDir.replace(/\\/g, '/'), relative).replace(/\//g, '\\');
199
168
  }
200
169
  const winChromePath = chromePath
201
170
  .replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\`)
202
171
  .replace(/\//g, '\\');
203
- const winProfile = path.posix.join(userProfileWin.replace(/\\/g, '/'), '.ai-extension-profile').replace(/\//g, '\\');
204
172
  await ctx.actions.runAction('core:log', { level: 'info', message: `WSL Launch Target (Win): ${finalWinExtensionPath}` });
205
173
  // await ctx.actions.runAction('core:log', { level: 'info', message: `WSL Profile (Win): ${winProfile}` });
206
174
  // Create PowerShell Launch Script
@@ -209,43 +177,75 @@ $chromePath = "${winChromePath}"
209
177
  $extPath = "${finalWinExtensionPath}"
210
178
  $profilePath = "${winProfile}"
211
179
 
180
+ Write-Host "DEBUG: ChromePath: $chromePath"
181
+ Write-Host "DEBUG: ExtPath: $extPath"
182
+ Write-Host "DEBUG: ProfilePath: $profilePath"
183
+
184
+ # Verify Paths
185
+ if (-not (Test-Path -Path $extPath)) {
186
+ Write-Host "ERROR: Extension Path NOT FOUND!"
187
+ } else {
188
+ Write-Host "DEBUG: Extension Path Exists."
189
+ }
190
+
212
191
  # Create Profile Dir if needed
213
192
  if (-not (Test-Path -Path $profilePath)) {
214
193
  New-Item -ItemType Directory -Force -Path $profilePath | Out-Null
215
194
  }
216
195
 
217
196
  $argsList = @(
218
- "--load-extension=$extPath",
219
- "--user-data-dir=$profilePath",
197
+ "--load-extension=""$extPath""",
198
+ "--user-data-dir=""$profilePath""",
220
199
  "--no-first-run",
221
200
  "--no-default-browser-check",
222
201
  "--disable-gpu",
223
- "chrome://extensions"
202
+ "about:blank"
224
203
  )
225
204
 
226
- Start-Process -FilePath $chromePath -ArgumentList $argsList
205
+ # Convert to single string to ensure Start-Process handles it safely
206
+ $argStr = $argsList -join " "
207
+ Write-Host "DEBUG: Args: $argStr"
208
+
209
+ Write-Host "DEBUG: Launching Chrome..."
210
+ Start-Process -FilePath $chromePath -ArgumentList $argStr
227
211
  `;
228
- const psPath = path.join(wslStagingDir, 'launch.ps1');
229
- const winPsPath = path.posix.join(stagingDirWin.replace(/\\/g, '/'), 'launch.ps1').replace(/\//g, '\\');
212
+ // Write ps1 to /mnt/c/Temp/ai-ext-preview/launch.ps1 (Same as STAGING_DIR)
213
+ const psPath = path.join(STAGING_DIR, 'launch.ps1');
230
214
  try {
231
215
  await fs.writeFile(psPath, psContent);
232
216
  }
233
217
  catch (e) {
234
218
  await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Write PS1 Failed: ${e.message}` });
235
219
  }
236
- // Execute PowerShell
237
- const cli = 'powershell.exe';
238
- try {
239
- const subprocess = spawn(cli, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', winPsPath], {
240
- detached: true, // We still detach the PowerShell process itself
241
- stdio: 'ignore',
242
- cwd: `/mnt/${driveLetter}`
220
+ // Execute via PowerShell (Spawn detached)
221
+ // psPathWin is C:\\Temp\\ai-ext-preview\\launch.ps1
222
+ const psPathWin = `${winStagingDir}\\launch.ps1`;
223
+ const child = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psPathWin], {
224
+ detached: true,
225
+ stdio: ['ignore', 'pipe', 'pipe'] // Pipe stderr AND stdout to catch launch errors/debug
226
+ });
227
+ if (child.stdout) {
228
+ child.stdout.on('data', async (chunk) => {
229
+ const msg = chunk.toString();
230
+ await ctx.actions.runAction('core:log', { level: 'info', message: `[PS1] ${msg.trim()}` });
243
231
  });
244
- subprocess.unref();
245
232
  }
246
- catch (spawnErr) {
247
- await ctx.actions.runAction('core:log', { level: 'error', message: `WSL Spawn Error: ${spawnErr.message}` });
233
+ if (child.stderr) {
234
+ child.stderr.on('data', async (chunk) => {
235
+ const msg = chunk.toString();
236
+ await ctx.actions.runAction('core:log', { level: 'error', message: `Launch Error (Stderr): ${msg}` });
237
+ if (msg.includes('Exec format error')) {
238
+ await ctx.actions.runAction('core:log', { level: 'error', message: `CRITICAL: WSL Interop is broken. Cannot launch Chrome.` });
239
+ await ctx.actions.runAction('core:log', { level: 'error', message: `FIX: Open PowerShell as Admin and run: wsl --shutdown` });
240
+ ctx.events.emit('browser:launch-failed', { reason: 'WSL_INTEROP_BROKEN' });
241
+ }
242
+ });
248
243
  }
244
+ child.on('error', async (err) => {
245
+ await ctx.actions.runAction('core:log', { level: 'error', message: `Launch Failed: ${err.message}` });
246
+ ctx.events.emit('browser:launch-failed', { reason: err.message });
247
+ });
248
+ child.unref();
249
249
  return true;
250
250
  }
251
251
  else {
@@ -32,7 +32,10 @@ export const CorePlugin = {
32
32
  break;
33
33
  default:
34
34
  logger.info(message);
35
+ break;
35
36
  }
37
+ // Emit event for UI
38
+ ctx.events.emit('log', { level, message, timestamp: new Date().toISOString() });
36
39
  return true;
37
40
  }
38
41
  });
@@ -66,8 +66,17 @@ export const DownloaderPlugin = {
66
66
  // First run, just verify it exists
67
67
  }
68
68
  if (job.status === 'completed') {
69
- if (newVersion !== lastModified) {
70
- await ctx.actions.runAction('core:log', { level: 'info', message: `New version detected (Old: "${lastModified}", New: "${newVersion}")` });
69
+ // Check if files actually exist
70
+ let forceDownload = false;
71
+ const manifestPath = path.join(DIST_DIR, 'manifest.json');
72
+ if (!fs.existsSync(manifestPath)) {
73
+ await ctx.actions.runAction('core:log', { level: 'warn', message: 'Version match but files missing. Forcing download...' });
74
+ forceDownload = true;
75
+ }
76
+ if (newVersion !== lastModified || forceDownload) {
77
+ if (newVersion !== lastModified) {
78
+ await ctx.actions.runAction('core:log', { level: 'info', message: `New version detected (Old: "${lastModified}", New: "${newVersion}")` });
79
+ }
71
80
  const success = await ctx.actions.runAction('downloader:download', null);
72
81
  if (success) {
73
82
  lastModified = newVersion;
@@ -186,16 +195,26 @@ console.log('[Hot Reload] Active for Job:', CURRENT_JOB_ID);
186
195
  }
187
196
  });
188
197
  // Start Polling (Loop)
189
- console.error('[DownloaderPlugin] Starting polling loop (Interval: 2000ms)');
198
+ void ctx.actions.runAction('core:log', { level: 'info', message: 'Starting polling loop (Interval: 2000ms)' });
199
+ // Listen for browser failure to stop polling
200
+ ctx.events.on('browser:launch-failed', () => {
201
+ if (checkInterval) {
202
+ clearInterval(checkInterval);
203
+ checkInterval = undefined;
204
+ ctx.actions.runAction('core:log', { level: 'warn', message: 'Polling stopped due to browser launch failure.' });
205
+ // Update status happens in UI
206
+ }
207
+ });
190
208
  checkInterval = setInterval(async () => {
191
209
  try {
192
- // Use actions for main log, but console.error for guaranteed debug output
193
- // await ctx.actions.runAction('core:log', { level: 'info', message: '[DEBUG] Polling Tick...' });
194
- console.error('[DownloaderPlugin] Tick - Checking Status...');
210
+ // Use actions for main log (UI Plugin captures this)
211
+ // console.error('[DownloaderPlugin] Tick - Checking Status...'); // REMOVE (Outside UI)
212
+ // Silent polling for CLI mode
213
+ // await ctx.actions.runAction('core:log', { level: 'info', message: '[DEBUG] Polling...' });
195
214
  await ctx.actions.runAction('downloader:check', null);
196
215
  }
197
216
  catch (err) {
198
- console.error('[DownloaderPlugin] Poll Error:', err);
217
+ await ctx.actions.runAction('core:log', { level: 'error', message: `Poll Error: ${err.message}` });
199
218
  }
200
219
  }, 2000);
201
220
  },
@@ -24,7 +24,7 @@ export const ServerPlugin = {
24
24
  }
25
25
  if (req.url === '/status') {
26
26
  const currentJobId = ctx.host.config.jobId;
27
- ctx.actions.runAction('core:log', { level: 'info', message: `[DEBUG] Server: Extension requested status (Reporting: ${currentVersion})` });
27
+ // ctx.actions.runAction('core:log', { level: 'info', message: `[DEBUG] Server: Extension requested status (Reporting: ${currentVersion})` });
28
28
  res.writeHead(200, { 'Content-Type': 'application/json' });
29
29
  res.end(JSON.stringify({
30
30
  version: currentVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-extension-preview",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Local preview tool for AI Extension Builder",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,4 +50,4 @@
50
50
  "typescript": "^5.7.2",
51
51
  "vitest": "^4.0.16"
52
52
  }
53
- }
53
+ }