visual-ai-staging 0.0.3 → 0.0.5
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/cli.js +131 -6
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -4,6 +4,41 @@ const http = require('http');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const url = require('url');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { exec } = require('child_process');
|
|
9
|
+
|
|
10
|
+
function openBrowser(targetUrl) {
|
|
11
|
+
const startCommand = process.platform === 'darwin' ? 'open' :
|
|
12
|
+
process.platform === 'win32' ? 'start ""' :
|
|
13
|
+
'xdg-open';
|
|
14
|
+
exec(`${startCommand} "${targetUrl}"`, (err) => {
|
|
15
|
+
// Silently ignore if default browser cannot be launched
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveStoragePath(originHeader, folderType) {
|
|
20
|
+
const resolvedBase = path.resolve(__dirname);
|
|
21
|
+
let hostname = '';
|
|
22
|
+
if (originHeader) {
|
|
23
|
+
try {
|
|
24
|
+
const parsed = new URL(originHeader);
|
|
25
|
+
hostname = parsed.hostname;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (originHeader.includes('localhost')) {
|
|
28
|
+
hostname = 'localhost';
|
|
29
|
+
} else if (originHeader.includes('127.0.0.1')) {
|
|
30
|
+
hostname = '127.0.0.1';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';
|
|
36
|
+
if (isLocal || !originHeader) {
|
|
37
|
+
return path.join(resolvedBase, '.ai-staging', folderType);
|
|
38
|
+
} else {
|
|
39
|
+
return path.join(resolvedBase, '.ai-staging', 'external', folderType);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
7
42
|
|
|
8
43
|
const args = process.argv.slice(2);
|
|
9
44
|
const command = args[0];
|
|
@@ -38,19 +73,45 @@ if (command === 'init') {
|
|
|
38
73
|
const resolvedBase = path.resolve(__dirname);
|
|
39
74
|
const audioDir = path.join(resolvedBase, '.ai-staging', 'audio');
|
|
40
75
|
const feedbackDir = path.join(resolvedBase, '.ai-staging', 'feedback');
|
|
76
|
+
const extAudioDir = path.join(resolvedBase, '.ai-staging', 'external', 'audio');
|
|
77
|
+
const extFeedbackDir = path.join(resolvedBase, '.ai-staging', 'external', 'feedback');
|
|
41
78
|
|
|
42
79
|
try {
|
|
43
|
-
let alreadyExists = fs.existsSync(audioDir) && fs.existsSync(feedbackDir);
|
|
80
|
+
let alreadyExists = fs.existsSync(audioDir) && fs.existsSync(feedbackDir) && fs.existsSync(extAudioDir) && fs.existsSync(extFeedbackDir);
|
|
44
81
|
|
|
45
82
|
fs.mkdirSync(audioDir, { recursive: true });
|
|
46
83
|
fs.mkdirSync(feedbackDir, { recursive: true });
|
|
84
|
+
fs.mkdirSync(extAudioDir, { recursive: true });
|
|
85
|
+
fs.mkdirSync(extFeedbackDir, { recursive: true });
|
|
47
86
|
|
|
48
87
|
// Create .gitkeep to ensure empty directories are tracked by Git
|
|
49
88
|
const gitkeepAudio = path.join(audioDir, '.gitkeep');
|
|
50
89
|
const gitkeepFeedback = path.join(feedbackDir, '.gitkeep');
|
|
90
|
+
const gitkeepExtAudio = path.join(extAudioDir, '.gitkeep');
|
|
91
|
+
const gitkeepExtFeedback = path.join(extFeedbackDir, '.gitkeep');
|
|
51
92
|
|
|
52
93
|
if (!fs.existsSync(gitkeepAudio)) fs.writeFileSync(gitkeepAudio, '');
|
|
53
94
|
if (!fs.existsSync(gitkeepFeedback)) fs.writeFileSync(gitkeepFeedback, '');
|
|
95
|
+
if (!fs.existsSync(gitkeepExtAudio)) fs.writeFileSync(gitkeepExtAudio, '');
|
|
96
|
+
if (!fs.existsSync(gitkeepExtFeedback)) fs.writeFileSync(gitkeepExtFeedback, '');
|
|
97
|
+
|
|
98
|
+
console.log(`
|
|
99
|
+
┌───────────────────────────────────────────────────────────┐
|
|
100
|
+
│ │
|
|
101
|
+
│ Welcome to Visual AI Staging! │
|
|
102
|
+
│ │
|
|
103
|
+
│ To start staging your active layouts in hot-memory: │
|
|
104
|
+
│ │
|
|
105
|
+
│ 1. Install the official companion browser extension: │
|
|
106
|
+
│ https://chrome.google.com/webstore │
|
|
107
|
+
│ │
|
|
108
|
+
│ 2. Start your local staging server by running: │
|
|
109
|
+
│ vais dev │
|
|
110
|
+
│ │
|
|
111
|
+
│ 3. Click the extension icon on any page to begin. │
|
|
112
|
+
│ │
|
|
113
|
+
└───────────────────────────────────────────────────────────┘
|
|
114
|
+
`);
|
|
54
115
|
|
|
55
116
|
if (alreadyExists) {
|
|
56
117
|
console.log('Visual AI Staging environment already initialized in this workspace.');
|
|
@@ -59,7 +120,12 @@ if (command === 'init') {
|
|
|
59
120
|
console.log('Created directories:');
|
|
60
121
|
console.log(' - .ai-staging/audio/');
|
|
61
122
|
console.log(' - .ai-staging/feedback/');
|
|
123
|
+
console.log(' - .ai-staging/external/audio/');
|
|
124
|
+
console.log(' - .ai-staging/external/feedback/');
|
|
62
125
|
}
|
|
126
|
+
|
|
127
|
+
console.log('\nOpening Chrome Web Store to download the companion extension...');
|
|
128
|
+
openBrowser('https://chrome.google.com/webstore');
|
|
63
129
|
} catch (err) {
|
|
64
130
|
console.error('Error initializing workspace:', err.message);
|
|
65
131
|
process.exit(1);
|
|
@@ -68,28 +134,72 @@ if (command === 'init') {
|
|
|
68
134
|
}
|
|
69
135
|
|
|
70
136
|
if (command === 'dev') {
|
|
71
|
-
|
|
137
|
+
let activePort = 3000;
|
|
72
138
|
const resolvedBase = path.resolve(__dirname);
|
|
73
139
|
|
|
74
140
|
// Auto-initialize directories silently on server startup if they do not exist
|
|
75
141
|
const audioDir = path.join(resolvedBase, '.ai-staging', 'audio');
|
|
76
142
|
const feedbackDir = path.join(resolvedBase, '.ai-staging', 'feedback');
|
|
143
|
+
const extAudioDir = path.join(resolvedBase, '.ai-staging', 'external', 'audio');
|
|
144
|
+
const extFeedbackDir = path.join(resolvedBase, '.ai-staging', 'external', 'feedback');
|
|
77
145
|
try {
|
|
78
146
|
fs.mkdirSync(audioDir, { recursive: true });
|
|
79
147
|
fs.mkdirSync(feedbackDir, { recursive: true });
|
|
148
|
+
fs.mkdirSync(extAudioDir, { recursive: true });
|
|
149
|
+
fs.mkdirSync(extFeedbackDir, { recursive: true });
|
|
80
150
|
|
|
81
151
|
const gitkeepAudio = path.join(audioDir, '.gitkeep');
|
|
82
152
|
const gitkeepFeedback = path.join(feedbackDir, '.gitkeep');
|
|
153
|
+
const gitkeepExtAudio = path.join(extAudioDir, '.gitkeep');
|
|
154
|
+
const gitkeepExtFeedback = path.join(extFeedbackDir, '.gitkeep');
|
|
83
155
|
if (!fs.existsSync(gitkeepAudio)) fs.writeFileSync(gitkeepAudio, '');
|
|
84
156
|
if (!fs.existsSync(gitkeepFeedback)) fs.writeFileSync(gitkeepFeedback, '');
|
|
157
|
+
if (!fs.existsSync(gitkeepExtAudio)) fs.writeFileSync(gitkeepExtAudio, '');
|
|
158
|
+
if (!fs.existsSync(gitkeepExtFeedback)) fs.writeFileSync(gitkeepExtFeedback, '');
|
|
85
159
|
} catch (err) {
|
|
86
160
|
console.warn('Warning: Failed to auto-initialize staging directories on startup:', err.message);
|
|
87
161
|
}
|
|
88
162
|
|
|
89
163
|
const server = http.createServer((req, res) => {
|
|
164
|
+
const origin = req.headers.origin || '*';
|
|
165
|
+
|
|
166
|
+
// CORS preflight OPTIONS request
|
|
167
|
+
if (req.method === 'OPTIONS') {
|
|
168
|
+
res.writeHead(204, {
|
|
169
|
+
'Access-Control-Allow-Origin': origin,
|
|
170
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
171
|
+
'Access-Control-Allow-Headers': 'Content-Type, X-Project-Origin',
|
|
172
|
+
'Access-Control-Allow-Credentials': 'true'
|
|
173
|
+
});
|
|
174
|
+
res.end();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
90
178
|
const parsedUrl = url.parse(req.url, true);
|
|
91
179
|
const pathname = parsedUrl.pathname;
|
|
92
180
|
|
|
181
|
+
// Apply CORS headers for save and session info API responses
|
|
182
|
+
if (pathname === '/api/save-audio' || pathname === '/api/save-feedback' || pathname === '/api/session-info') {
|
|
183
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
184
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
185
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Project-Origin');
|
|
186
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle GET /api/session-info
|
|
190
|
+
if (req.method === 'GET' && pathname === '/api/session-info') {
|
|
191
|
+
res.statusCode = 200;
|
|
192
|
+
res.setHeader('Content-Type', 'application/json');
|
|
193
|
+
const projectName = path.basename(resolvedBase);
|
|
194
|
+
res.end(JSON.stringify({
|
|
195
|
+
success: true,
|
|
196
|
+
port: activePort,
|
|
197
|
+
projectName: projectName,
|
|
198
|
+
projectPath: resolvedBase
|
|
199
|
+
}));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
93
203
|
// Handle POST /api/save-audio?filename=<encoded_filename>
|
|
94
204
|
if (req.method === 'POST' && pathname === '/api/save-audio') {
|
|
95
205
|
const rawFilename = parsedUrl.query.filename || '';
|
|
@@ -103,7 +213,7 @@ if (command === 'dev') {
|
|
|
103
213
|
return;
|
|
104
214
|
}
|
|
105
215
|
|
|
106
|
-
const targetDir =
|
|
216
|
+
const targetDir = resolveStoragePath(req.headers.origin, 'audio');
|
|
107
217
|
try {
|
|
108
218
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
109
219
|
} catch (err) {
|
|
@@ -153,7 +263,7 @@ if (command === 'dev') {
|
|
|
153
263
|
return;
|
|
154
264
|
}
|
|
155
265
|
|
|
156
|
-
const targetDir =
|
|
266
|
+
const targetDir = resolveStoragePath(req.headers.origin, 'feedback');
|
|
157
267
|
try {
|
|
158
268
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
159
269
|
} catch (err) {
|
|
@@ -232,9 +342,24 @@ if (command === 'dev') {
|
|
|
232
342
|
res.end('405 Method Not Allowed');
|
|
233
343
|
});
|
|
234
344
|
|
|
235
|
-
|
|
236
|
-
|
|
345
|
+
function startServer(p) {
|
|
346
|
+
server.listen(p, () => {
|
|
347
|
+
console.log(`Visual AI Staging Dev Server running at http://localhost:${p}/`);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
server.on('error', (err) => {
|
|
352
|
+
if (err.code === 'EADDRINUSE') {
|
|
353
|
+
console.log(`Port ${activePort} is occupied. Retrying on next port...`);
|
|
354
|
+
activePort++;
|
|
355
|
+
startServer(activePort);
|
|
356
|
+
} else {
|
|
357
|
+
console.error('Server error:', err.message);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
237
360
|
});
|
|
361
|
+
|
|
362
|
+
startServer(activePort);
|
|
238
363
|
} else {
|
|
239
364
|
console.error(`Unknown command: ${command}`);
|
|
240
365
|
console.error('Run "vais --help" for usage.');
|