pinokiod 3.271.0 → 3.272.0
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/kernel/ansi_stream_tracker.js +115 -0
- package/kernel/api/app/index.js +422 -0
- package/kernel/api/htmlmodal/index.js +94 -0
- package/kernel/app_launcher/index.js +115 -0
- package/kernel/app_launcher/platform/base.js +276 -0
- package/kernel/app_launcher/platform/linux.js +229 -0
- package/kernel/app_launcher/platform/macos.js +163 -0
- package/kernel/app_launcher/platform/unsupported.js +34 -0
- package/kernel/app_launcher/platform/windows.js +247 -0
- package/kernel/bin/conda-meta.js +93 -0
- package/kernel/bin/conda.js +2 -4
- package/kernel/bin/index.js +2 -4
- package/kernel/index.js +7 -0
- package/kernel/shell.js +212 -1
- package/package.json +1 -1
- package/server/index.js +491 -6
- package/server/public/common.js +224 -741
- package/server/public/create-launcher.js +754 -0
- package/server/public/htmlmodal.js +292 -0
- package/server/public/logs.js +715 -0
- package/server/public/resizeSync.js +117 -0
- package/server/public/style.css +651 -6
- package/server/public/tab-idle-notifier.js +34 -59
- package/server/public/tab-link-popover.js +7 -10
- package/server/public/terminal-settings.js +723 -9
- package/server/public/terminal_input_utils.js +72 -0
- package/server/public/terminal_key_caption.js +187 -0
- package/server/public/urldropdown.css +120 -3
- package/server/public/xterm-inline-bridge.js +116 -0
- package/server/socket.js +29 -0
- package/server/views/agents.ejs +1 -2
- package/server/views/app.ejs +55 -28
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -0
- package/server/views/connect.ejs +1 -2
- package/server/views/create.ejs +63 -0
- package/server/views/editor.ejs +36 -4
- package/server/views/index.ejs +1 -2
- package/server/views/index2.ejs +1 -2
- package/server/views/init/index.ejs +36 -28
- package/server/views/install.ejs +20 -22
- package/server/views/layout.ejs +2 -8
- package/server/views/logs.ejs +155 -0
- package/server/views/mini.ejs +0 -18
- package/server/views/net.ejs +2 -2
- package/server/views/network.ejs +1 -2
- package/server/views/network2.ejs +1 -2
- package/server/views/old_network.ejs +1 -2
- package/server/views/pro.ejs +26 -23
- package/server/views/prototype/index.ejs +30 -34
- package/server/views/screenshots.ejs +1 -2
- package/server/views/settings.ejs +1 -20
- package/server/views/shell.ejs +59 -66
- package/server/views/terminal.ejs +118 -73
- package/server/views/tools.ejs +1 -2
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
(function(window, document) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
if (window.CreateLauncher) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const FALLBACK_TOOLS = [
|
|
9
|
+
{
|
|
10
|
+
value: 'claude',
|
|
11
|
+
label: 'Claude Code',
|
|
12
|
+
iconSrc: '/asset/plugin/code/claude/claude.png',
|
|
13
|
+
isDefault: true,
|
|
14
|
+
href: '/run/plugin/code/claude/pinokio.js',
|
|
15
|
+
category: 'CLI',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: 'codex',
|
|
19
|
+
label: 'OpenAI Codex',
|
|
20
|
+
iconSrc: '/asset/plugin/code/codex/openai.webp',
|
|
21
|
+
isDefault: false,
|
|
22
|
+
href: '/run/plugin/code/codex/pinokio.js',
|
|
23
|
+
category: 'CLI',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: 'gemini',
|
|
27
|
+
label: 'Google Gemini CLI',
|
|
28
|
+
iconSrc: '/asset/plugin/code/gemini/gemini.jpeg',
|
|
29
|
+
isDefault: false,
|
|
30
|
+
href: '/run/plugin/code/gemini/pinokio.js',
|
|
31
|
+
category: 'CLI',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const CATEGORY_ORDER = ['CLI', 'IDE'];
|
|
36
|
+
|
|
37
|
+
let cachedTools = null;
|
|
38
|
+
let loadingTools = null;
|
|
39
|
+
let modalInstance = null;
|
|
40
|
+
let modalPromise = null;
|
|
41
|
+
let modalKeydownHandler = null;
|
|
42
|
+
|
|
43
|
+
function mapPluginMenuToCreateLauncherTools(menu) {
|
|
44
|
+
if (!Array.isArray(menu)) return [];
|
|
45
|
+
|
|
46
|
+
return menu
|
|
47
|
+
.map((plugin) => {
|
|
48
|
+
if (!plugin || (!plugin.href && !plugin.link)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const href = typeof plugin.href === 'string' ? plugin.href.trim() : '';
|
|
52
|
+
const label = plugin.title || plugin.text || plugin.name || href || '';
|
|
53
|
+
|
|
54
|
+
let slug = '';
|
|
55
|
+
if (href) {
|
|
56
|
+
const segments = href.split('/').filter(Boolean);
|
|
57
|
+
if (segments.length >= 2) {
|
|
58
|
+
slug = segments[segments.length - 2] || '';
|
|
59
|
+
}
|
|
60
|
+
if (!slug && segments.length) {
|
|
61
|
+
slug = segments[segments.length - 1] || '';
|
|
62
|
+
}
|
|
63
|
+
if (slug.endsWith('.js')) {
|
|
64
|
+
slug = slug.replace(/\.js$/i, '');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!slug && label) {
|
|
68
|
+
slug = label
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
71
|
+
.replace(/^-+|-+$/g, '');
|
|
72
|
+
}
|
|
73
|
+
const value = slug || href || (typeof plugin.link === 'string' ? plugin.link.trim() : '');
|
|
74
|
+
if (!value) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const iconSrc = plugin.image || null;
|
|
78
|
+
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
79
|
+
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
80
|
+
const category = hasExec ? 'IDE' : 'CLI';
|
|
81
|
+
return {
|
|
82
|
+
value,
|
|
83
|
+
label,
|
|
84
|
+
iconSrc,
|
|
85
|
+
isDefault: Boolean(plugin.default === true),
|
|
86
|
+
href: href || null,
|
|
87
|
+
category,
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
.filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function getCreateLauncherTools() {
|
|
94
|
+
if (Array.isArray(cachedTools) && cachedTools.length > 0) {
|
|
95
|
+
return cachedTools;
|
|
96
|
+
}
|
|
97
|
+
if (loadingTools) {
|
|
98
|
+
return loadingTools;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
loadingTools = fetch('/api/plugin/menu')
|
|
102
|
+
.then((res) => {
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
throw new Error(`Failed to load plugin menu: ${res.status}`);
|
|
105
|
+
}
|
|
106
|
+
return res.json();
|
|
107
|
+
})
|
|
108
|
+
.then((data) => {
|
|
109
|
+
const menu = data && Array.isArray(data.menu) ? data.menu : [];
|
|
110
|
+
const tools = mapPluginMenuToCreateLauncherTools(menu);
|
|
111
|
+
return tools.length > 0 ? tools : FALLBACK_TOOLS.slice();
|
|
112
|
+
})
|
|
113
|
+
.catch((error) => {
|
|
114
|
+
console.warn('Falling back to default agents for create launcher modal', error);
|
|
115
|
+
return FALLBACK_TOOLS.slice();
|
|
116
|
+
})
|
|
117
|
+
.finally(() => {
|
|
118
|
+
loadingTools = null;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const tools = await loadingTools;
|
|
122
|
+
cachedTools = tools;
|
|
123
|
+
return tools;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function generateFolderSuggestion(prompt) {
|
|
127
|
+
if (!prompt) return '';
|
|
128
|
+
return prompt
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.replace(/[^a-z0-9\-\s_]/g, '')
|
|
131
|
+
.replace(/[\s_]+/g, '-')
|
|
132
|
+
.replace(/^-+|-+$/g, '')
|
|
133
|
+
.slice(0, 50);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateToolSelections(entries) {
|
|
137
|
+
entries.forEach(({ input, container }) => {
|
|
138
|
+
if (input.checked) {
|
|
139
|
+
container.classList.add('selected');
|
|
140
|
+
} else {
|
|
141
|
+
container.classList.remove('selected');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function extractTemplateVariableNames(template) {
|
|
147
|
+
const regex = /{{\s*([a-zA-Z0-9_][a-zA-Z0-9_\-.]*)\s*}}/g;
|
|
148
|
+
const names = new Set();
|
|
149
|
+
if (!template) return [];
|
|
150
|
+
let match;
|
|
151
|
+
while ((match = regex.exec(template)) !== null) {
|
|
152
|
+
names.add(match[1]);
|
|
153
|
+
}
|
|
154
|
+
return Array.from(names);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function escapeRegExp(str) {
|
|
158
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function applyTemplateValues(template, values) {
|
|
162
|
+
if (!template) return '';
|
|
163
|
+
let result = template;
|
|
164
|
+
values.forEach((value, name) => {
|
|
165
|
+
const pattern = new RegExp(`{{\\s*${escapeRegExp(name)}\\s*}}`, 'g');
|
|
166
|
+
result = result.replace(pattern, value);
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildToolOptions(tools) {
|
|
172
|
+
const wrapper = document.createElement('div');
|
|
173
|
+
wrapper.className = 'create-launcher-modal-tools';
|
|
174
|
+
|
|
175
|
+
const title = document.createElement('div');
|
|
176
|
+
title.className = 'create-launcher-modal-tools-title';
|
|
177
|
+
title.textContent = 'Select Agent';
|
|
178
|
+
|
|
179
|
+
const options = document.createElement('div');
|
|
180
|
+
options.className = 'create-launcher-modal-tools-options';
|
|
181
|
+
|
|
182
|
+
const toolEntries = [];
|
|
183
|
+
const defaultToolIndex = tools.findIndex((tool) => tool.isDefault);
|
|
184
|
+
const fallbackIndex = defaultToolIndex >= 0 ? defaultToolIndex : (tools.length > 0 ? 0 : -1);
|
|
185
|
+
|
|
186
|
+
const grouped = tools.reduce((acc, tool, index) => {
|
|
187
|
+
const category = tool.category || 'CLI';
|
|
188
|
+
if (!acc.has(category)) {
|
|
189
|
+
acc.set(category, []);
|
|
190
|
+
}
|
|
191
|
+
acc.get(category).push({ tool, index });
|
|
192
|
+
return acc;
|
|
193
|
+
}, new Map());
|
|
194
|
+
|
|
195
|
+
const orderedGroups = [];
|
|
196
|
+
CATEGORY_ORDER.forEach((category) => {
|
|
197
|
+
if (grouped.has(category)) {
|
|
198
|
+
orderedGroups.push([category, grouped.get(category)]);
|
|
199
|
+
grouped.delete(category);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
grouped.forEach((value, key) => {
|
|
203
|
+
orderedGroups.push([key, value]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
orderedGroups.forEach(([category, entries]) => {
|
|
207
|
+
const group = document.createElement('div');
|
|
208
|
+
group.className = 'create-launcher-modal-tools-group';
|
|
209
|
+
|
|
210
|
+
const heading = document.createElement('div');
|
|
211
|
+
heading.className = 'create-launcher-modal-tools-group-title';
|
|
212
|
+
heading.textContent = category;
|
|
213
|
+
group.appendChild(heading);
|
|
214
|
+
|
|
215
|
+
const list = document.createElement('div');
|
|
216
|
+
list.className = 'create-launcher-modal-tools-group-options';
|
|
217
|
+
|
|
218
|
+
const sortedEntries = entries.slice().sort((a, b) => {
|
|
219
|
+
const nameA = (a.tool && a.tool.label ? a.tool.label : '').toLowerCase();
|
|
220
|
+
const nameB = (b.tool && b.tool.label ? b.tool.label : '').toLowerCase();
|
|
221
|
+
if (nameA < nameB) return -1;
|
|
222
|
+
if (nameA > nameB) return 1;
|
|
223
|
+
return 0;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
sortedEntries.forEach(({ tool, index }) => {
|
|
227
|
+
const option = document.createElement('label');
|
|
228
|
+
option.className = 'create-launcher-modal-tool';
|
|
229
|
+
|
|
230
|
+
const radio = document.createElement('input');
|
|
231
|
+
radio.type = 'radio';
|
|
232
|
+
radio.name = 'create-launcher-tool';
|
|
233
|
+
radio.value = tool.value;
|
|
234
|
+
radio.dataset.agentLabel = tool.label;
|
|
235
|
+
radio.dataset.agentCategory = category;
|
|
236
|
+
if (tool.href) {
|
|
237
|
+
radio.dataset.agentHref = tool.href;
|
|
238
|
+
}
|
|
239
|
+
if (index === fallbackIndex) {
|
|
240
|
+
radio.checked = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const badge = document.createElement('span');
|
|
244
|
+
badge.className = 'create-launcher-modal-tool-label';
|
|
245
|
+
badge.textContent = tool.label;
|
|
246
|
+
|
|
247
|
+
option.appendChild(radio);
|
|
248
|
+
if (tool.iconSrc) {
|
|
249
|
+
const icon = document.createElement('img');
|
|
250
|
+
icon.className = 'create-launcher-modal-tool-icon';
|
|
251
|
+
icon.src = tool.iconSrc;
|
|
252
|
+
icon.alt = `${tool.label} icon`;
|
|
253
|
+
icon.onerror = () => { icon.style.display = 'none'; };
|
|
254
|
+
option.appendChild(icon);
|
|
255
|
+
}
|
|
256
|
+
option.appendChild(badge);
|
|
257
|
+
list.appendChild(option);
|
|
258
|
+
|
|
259
|
+
const entry = { input: radio, container: option, meta: tool };
|
|
260
|
+
toolEntries.push(entry);
|
|
261
|
+
radio.addEventListener('change', () => updateToolSelections(toolEntries));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
group.appendChild(list);
|
|
265
|
+
options.appendChild(group);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!toolEntries.length) {
|
|
269
|
+
const emptyState = document.createElement('div');
|
|
270
|
+
emptyState.className = 'create-launcher-modal-tools-empty';
|
|
271
|
+
emptyState.textContent = 'No agents available.';
|
|
272
|
+
options.appendChild(emptyState);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
wrapper.appendChild(title);
|
|
276
|
+
wrapper.appendChild(options);
|
|
277
|
+
|
|
278
|
+
return { wrapper, toolEntries };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function createTemplateManager(templateWrapper, templateFields) {
|
|
282
|
+
let templateValues = new Map();
|
|
283
|
+
|
|
284
|
+
function syncTemplateFields(promptText, defaults = {}) {
|
|
285
|
+
const variableNames = extractTemplateVariableNames(promptText);
|
|
286
|
+
const previousValues = templateValues;
|
|
287
|
+
const newValues = new Map();
|
|
288
|
+
|
|
289
|
+
variableNames.forEach((name) => {
|
|
290
|
+
if (Object.prototype.hasOwnProperty.call(defaults, name) && defaults[name] !== undefined) {
|
|
291
|
+
newValues.set(name, defaults[name]);
|
|
292
|
+
} else if (previousValues.has(name)) {
|
|
293
|
+
newValues.set(name, previousValues.get(name));
|
|
294
|
+
} else {
|
|
295
|
+
newValues.set(name, '');
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
templateValues = newValues;
|
|
300
|
+
templateFields.innerHTML = '';
|
|
301
|
+
|
|
302
|
+
if (variableNames.length === 0) {
|
|
303
|
+
templateWrapper.style.display = 'none';
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
templateWrapper.style.display = 'flex';
|
|
308
|
+
|
|
309
|
+
variableNames.forEach((name) => {
|
|
310
|
+
const field = document.createElement('label');
|
|
311
|
+
field.className = 'create-launcher-modal-template-field';
|
|
312
|
+
|
|
313
|
+
const labelText = document.createElement('span');
|
|
314
|
+
labelText.className = 'create-launcher-modal-template-field-label';
|
|
315
|
+
labelText.textContent = name;
|
|
316
|
+
|
|
317
|
+
const input = document.createElement('input');
|
|
318
|
+
input.type = 'text';
|
|
319
|
+
input.className = 'create-launcher-modal-template-input';
|
|
320
|
+
input.placeholder = `Enter ${name}`;
|
|
321
|
+
input.value = templateValues.get(name) || '';
|
|
322
|
+
input.dataset.templateInput = name;
|
|
323
|
+
input.addEventListener('input', () => {
|
|
324
|
+
templateValues.set(name, input.value);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
field.appendChild(labelText);
|
|
328
|
+
field.appendChild(input);
|
|
329
|
+
templateFields.appendChild(field);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getTemplateValues() {
|
|
334
|
+
return new Map(templateValues);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function setTemplateValues(values = {}) {
|
|
338
|
+
if (!values || typeof values !== 'object') {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
templateValues.forEach((_, key) => {
|
|
342
|
+
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
|
343
|
+
templateValues.set(key, values[key]);
|
|
344
|
+
const input = templateFields.querySelector(`[data-template-input="${key}"]`);
|
|
345
|
+
if (input) {
|
|
346
|
+
input.value = values[key];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { syncTemplateFields, getTemplateValues, setTemplateValues };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function buildCreateLauncherUI({ mode = 'modal', tools }) {
|
|
356
|
+
const isPage = mode === 'page';
|
|
357
|
+
const overlay = isPage ? null : document.createElement('div');
|
|
358
|
+
if (overlay) {
|
|
359
|
+
overlay.className = 'modal-overlay create-launcher-modal-overlay';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const container = document.createElement('div');
|
|
363
|
+
container.className = isPage ? 'create-launcher-page-card' : 'create-launcher-modal';
|
|
364
|
+
|
|
365
|
+
const header = document.createElement('div');
|
|
366
|
+
header.className = 'create-launcher-modal-header';
|
|
367
|
+
|
|
368
|
+
const iconWrapper = document.createElement('div');
|
|
369
|
+
iconWrapper.className = 'create-launcher-modal-icon';
|
|
370
|
+
|
|
371
|
+
const headerIcon = document.createElement('i');
|
|
372
|
+
headerIcon.className = 'fa-solid fa-wand-magic-sparkles';
|
|
373
|
+
iconWrapper.appendChild(headerIcon);
|
|
374
|
+
|
|
375
|
+
const headingStack = document.createElement('div');
|
|
376
|
+
headingStack.className = 'create-launcher-modal-headings';
|
|
377
|
+
|
|
378
|
+
const title = document.createElement('h3');
|
|
379
|
+
title.id = `${mode}-create-launcher-title`;
|
|
380
|
+
title.textContent = 'Create';
|
|
381
|
+
|
|
382
|
+
const description = document.createElement('p');
|
|
383
|
+
description.className = 'create-launcher-modal-description';
|
|
384
|
+
description.id = `${mode}-create-launcher-description`;
|
|
385
|
+
description.textContent = 'Create a reusable and shareable launcher for any task or any app';
|
|
386
|
+
|
|
387
|
+
headingStack.appendChild(title);
|
|
388
|
+
headingStack.appendChild(description);
|
|
389
|
+
|
|
390
|
+
header.appendChild(iconWrapper);
|
|
391
|
+
header.appendChild(headingStack);
|
|
392
|
+
|
|
393
|
+
let closeButton = null;
|
|
394
|
+
if (!isPage) {
|
|
395
|
+
closeButton = document.createElement('button');
|
|
396
|
+
closeButton.type = 'button';
|
|
397
|
+
closeButton.className = 'create-launcher-modal-close';
|
|
398
|
+
closeButton.setAttribute('aria-label', 'Close create launcher modal');
|
|
399
|
+
closeButton.innerHTML = '<i class="fa-solid fa-xmark"></i>';
|
|
400
|
+
header.appendChild(closeButton);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const promptLabel = document.createElement('label');
|
|
404
|
+
promptLabel.className = 'create-launcher-modal-label';
|
|
405
|
+
promptLabel.textContent = 'What do you want to do?';
|
|
406
|
+
|
|
407
|
+
const promptTextarea = document.createElement('textarea');
|
|
408
|
+
promptTextarea.className = 'create-launcher-modal-textarea';
|
|
409
|
+
promptTextarea.placeholder = 'Examples: "a 1-click launcher for ComfyUI", "I want to change file format", "I want to clone a website to run locally", etc. (Leave empty to decide later)';
|
|
410
|
+
promptLabel.appendChild(promptTextarea);
|
|
411
|
+
|
|
412
|
+
const templateWrapper = document.createElement('div');
|
|
413
|
+
templateWrapper.className = 'create-launcher-modal-template';
|
|
414
|
+
templateWrapper.style.display = 'none';
|
|
415
|
+
|
|
416
|
+
const templateTitle = document.createElement('div');
|
|
417
|
+
templateTitle.className = 'create-launcher-modal-template-title';
|
|
418
|
+
templateTitle.textContent = 'Template variables';
|
|
419
|
+
|
|
420
|
+
const templateDescription = document.createElement('p');
|
|
421
|
+
templateDescription.className = 'create-launcher-modal-template-description';
|
|
422
|
+
templateDescription.textContent = 'Fill in each variable below before creating your launcher.';
|
|
423
|
+
|
|
424
|
+
const templateFields = document.createElement('div');
|
|
425
|
+
templateFields.className = 'create-launcher-modal-template-fields';
|
|
426
|
+
|
|
427
|
+
templateWrapper.appendChild(templateTitle);
|
|
428
|
+
templateWrapper.appendChild(templateDescription);
|
|
429
|
+
templateWrapper.appendChild(templateFields);
|
|
430
|
+
|
|
431
|
+
const folderLabel = document.createElement('label');
|
|
432
|
+
folderLabel.className = 'create-launcher-modal-label';
|
|
433
|
+
folderLabel.textContent = 'name';
|
|
434
|
+
|
|
435
|
+
const folderInput = document.createElement('input');
|
|
436
|
+
folderInput.type = 'text';
|
|
437
|
+
folderInput.placeholder = 'example: my-launcher';
|
|
438
|
+
folderInput.className = 'create-launcher-modal-input';
|
|
439
|
+
folderLabel.appendChild(folderInput);
|
|
440
|
+
|
|
441
|
+
const { wrapper: toolWrapper, toolEntries } = buildToolOptions(tools);
|
|
442
|
+
|
|
443
|
+
const error = document.createElement('div');
|
|
444
|
+
error.className = 'create-launcher-modal-error';
|
|
445
|
+
|
|
446
|
+
const actions = document.createElement('div');
|
|
447
|
+
actions.className = 'create-launcher-modal-actions';
|
|
448
|
+
|
|
449
|
+
let cancelButton = null;
|
|
450
|
+
if (!isPage) {
|
|
451
|
+
cancelButton = document.createElement('button');
|
|
452
|
+
cancelButton.type = 'button';
|
|
453
|
+
cancelButton.className = 'create-launcher-modal-button cancel';
|
|
454
|
+
cancelButton.textContent = 'Cancel';
|
|
455
|
+
actions.appendChild(cancelButton);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const confirmButton = document.createElement('button');
|
|
459
|
+
confirmButton.type = 'button';
|
|
460
|
+
confirmButton.className = 'create-launcher-modal-button confirm';
|
|
461
|
+
confirmButton.textContent = 'Create';
|
|
462
|
+
actions.appendChild(confirmButton);
|
|
463
|
+
|
|
464
|
+
const advancedLink = document.createElement('a');
|
|
465
|
+
advancedLink.className = 'create-launcher-modal-advanced';
|
|
466
|
+
advancedLink.href = '/init';
|
|
467
|
+
advancedLink.textContent = 'Or, try advanced options';
|
|
468
|
+
|
|
469
|
+
const bookmarkletLink = document.createElement('a');
|
|
470
|
+
bookmarkletLink.className = 'create-launcher-modal-bookmarklet';
|
|
471
|
+
bookmarkletLink.href = '/bookmarklet';
|
|
472
|
+
bookmarkletLink.textContent = 'Bookmark this in your web browser';
|
|
473
|
+
|
|
474
|
+
const linkRow = document.createElement('div');
|
|
475
|
+
linkRow.className = 'create-launcher-modal-links';
|
|
476
|
+
linkRow.appendChild(advancedLink);
|
|
477
|
+
linkRow.appendChild(bookmarkletLink);
|
|
478
|
+
|
|
479
|
+
container.appendChild(header);
|
|
480
|
+
container.appendChild(promptLabel);
|
|
481
|
+
container.appendChild(templateWrapper);
|
|
482
|
+
container.appendChild(folderLabel);
|
|
483
|
+
container.appendChild(toolWrapper);
|
|
484
|
+
container.appendChild(error);
|
|
485
|
+
container.appendChild(actions);
|
|
486
|
+
container.appendChild(linkRow);
|
|
487
|
+
|
|
488
|
+
if (overlay) {
|
|
489
|
+
overlay.appendChild(container);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const templateManager = createTemplateManager(templateWrapper, templateFields);
|
|
493
|
+
|
|
494
|
+
let folderEditedByUser = false;
|
|
495
|
+
|
|
496
|
+
folderInput.addEventListener('input', () => {
|
|
497
|
+
folderEditedByUser = true;
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
promptTextarea.addEventListener('input', () => {
|
|
501
|
+
templateManager.syncTemplateFields(promptTextarea.value);
|
|
502
|
+
if (!folderEditedByUser) {
|
|
503
|
+
folderInput.value = generateFolderSuggestion(promptTextarea.value);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
mode,
|
|
509
|
+
overlay,
|
|
510
|
+
container,
|
|
511
|
+
promptTextarea,
|
|
512
|
+
folderInput,
|
|
513
|
+
templateWrapper,
|
|
514
|
+
templateFields,
|
|
515
|
+
templateManager,
|
|
516
|
+
toolEntries,
|
|
517
|
+
error,
|
|
518
|
+
cancelButton,
|
|
519
|
+
confirmButton,
|
|
520
|
+
closeButton,
|
|
521
|
+
advancedLink,
|
|
522
|
+
bookmarkletLink,
|
|
523
|
+
resetFolderTracking() {
|
|
524
|
+
folderEditedByUser = false;
|
|
525
|
+
},
|
|
526
|
+
markFolderEdited() {
|
|
527
|
+
folderEditedByUser = true;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function applyDefaultsToUi(ui, defaults = {}) {
|
|
533
|
+
if (!ui) return;
|
|
534
|
+
const promptValue = typeof defaults.prompt === 'string' ? defaults.prompt : '';
|
|
535
|
+
const folderValue = typeof defaults.folder === 'string' && defaults.folder.trim()
|
|
536
|
+
? defaults.folder.trim()
|
|
537
|
+
: generateFolderSuggestion(promptValue);
|
|
538
|
+
const toolValue = typeof defaults.tool === 'string' ? defaults.tool.trim() : '';
|
|
539
|
+
const templateDefaults = defaults.templateValues || {};
|
|
540
|
+
|
|
541
|
+
ui.promptTextarea.value = promptValue;
|
|
542
|
+
ui.templateManager.syncTemplateFields(promptValue, templateDefaults);
|
|
543
|
+
ui.folderInput.value = folderValue || '';
|
|
544
|
+
ui.resetFolderTracking();
|
|
545
|
+
|
|
546
|
+
if (toolValue) {
|
|
547
|
+
let matched = false;
|
|
548
|
+
ui.toolEntries.forEach((entry, index) => {
|
|
549
|
+
if (entry.input.value === toolValue) {
|
|
550
|
+
entry.input.checked = true;
|
|
551
|
+
matched = true;
|
|
552
|
+
} else {
|
|
553
|
+
entry.input.checked = false;
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
if (!matched && ui.toolEntries.length > 0) {
|
|
557
|
+
ui.toolEntries.forEach((entry, index) => {
|
|
558
|
+
entry.input.checked = index === 0;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
} else if (ui.toolEntries.length > 0) {
|
|
562
|
+
const defaultEntryIndex = ui.toolEntries.findIndex((entry) => entry.meta && entry.meta.isDefault);
|
|
563
|
+
const fallbackIndex = defaultEntryIndex >= 0 ? defaultEntryIndex : 0;
|
|
564
|
+
ui.toolEntries.forEach((entry, index) => {
|
|
565
|
+
entry.input.checked = index === fallbackIndex;
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
updateToolSelections(ui.toolEntries);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function readTemplateValues(ui) {
|
|
573
|
+
return ui && ui.templateManager ? ui.templateManager.getTemplateValues() : new Map();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function getSelectedTool(ui) {
|
|
577
|
+
if (!ui || !Array.isArray(ui.toolEntries)) {
|
|
578
|
+
return '';
|
|
579
|
+
}
|
|
580
|
+
const checked = ui.toolEntries.find((entry) => entry.input.checked);
|
|
581
|
+
if (checked && checked.input && checked.input.value) {
|
|
582
|
+
return checked.input.value;
|
|
583
|
+
}
|
|
584
|
+
return ui.toolEntries.length > 0 ? (ui.toolEntries[0].input.value || '') : '';
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function submitFromUi(ui) {
|
|
588
|
+
if (!ui) return;
|
|
589
|
+
ui.error.textContent = '';
|
|
590
|
+
|
|
591
|
+
const folderName = ui.folderInput.value.trim();
|
|
592
|
+
const rawPrompt = ui.promptTextarea.value;
|
|
593
|
+
const templateValues = readTemplateValues(ui);
|
|
594
|
+
const selectedTool = getSelectedTool(ui);
|
|
595
|
+
|
|
596
|
+
if (!selectedTool) {
|
|
597
|
+
ui.error.textContent = 'Please select an agent.';
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (!folderName) {
|
|
602
|
+
ui.error.textContent = 'Please enter a folder name.';
|
|
603
|
+
ui.folderInput.focus();
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (folderName.includes(' ')) {
|
|
608
|
+
ui.error.textContent = 'Folder names cannot contain spaces.';
|
|
609
|
+
ui.folderInput.focus();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let finalPrompt = rawPrompt;
|
|
614
|
+
if (templateValues.size > 0) {
|
|
615
|
+
const missingVariables = [];
|
|
616
|
+
templateValues.forEach((value, name) => {
|
|
617
|
+
if (!value || value.trim() === '') {
|
|
618
|
+
missingVariables.push(name);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
if (missingVariables.length > 0) {
|
|
623
|
+
ui.error.textContent = `Please fill in values for: ${missingVariables.join(', ')}`;
|
|
624
|
+
const targetInput = ui.templateFields?.querySelector(`[data-template-input="${missingVariables[0]}"]`);
|
|
625
|
+
if (targetInput) {
|
|
626
|
+
targetInput.focus();
|
|
627
|
+
} else {
|
|
628
|
+
ui.promptTextarea.focus();
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
finalPrompt = applyTemplateValues(rawPrompt, templateValues);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const prompt = finalPrompt.trim();
|
|
637
|
+
const params = new URLSearchParams();
|
|
638
|
+
params.set('name', folderName);
|
|
639
|
+
params.set('message', prompt);
|
|
640
|
+
if (selectedTool) {
|
|
641
|
+
params.set('tool', selectedTool);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
window.location.href = `/pro?${params.toString()}`;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function ensureCreateLauncherModal() {
|
|
648
|
+
if (modalInstance) {
|
|
649
|
+
return modalInstance;
|
|
650
|
+
}
|
|
651
|
+
if (modalPromise) {
|
|
652
|
+
return modalPromise;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
modalPromise = (async () => {
|
|
656
|
+
const tools = await getCreateLauncherTools();
|
|
657
|
+
const ui = buildCreateLauncherUI({ mode: 'modal', tools });
|
|
658
|
+
|
|
659
|
+
document.body.appendChild(ui.overlay);
|
|
660
|
+
|
|
661
|
+
ui.confirmButton.addEventListener('click', () => submitFromUi(ui));
|
|
662
|
+
if (ui.cancelButton) {
|
|
663
|
+
ui.cancelButton.addEventListener('click', hideModal);
|
|
664
|
+
}
|
|
665
|
+
if (ui.closeButton) {
|
|
666
|
+
ui.closeButton.addEventListener('click', hideModal);
|
|
667
|
+
}
|
|
668
|
+
ui.advancedLink.addEventListener('click', hideModal);
|
|
669
|
+
ui.bookmarkletLink.addEventListener('click', hideModal);
|
|
670
|
+
|
|
671
|
+
modalInstance = ui;
|
|
672
|
+
modalPromise = null;
|
|
673
|
+
return ui;
|
|
674
|
+
})();
|
|
675
|
+
|
|
676
|
+
return modalPromise;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function showModal(defaults = {}) {
|
|
680
|
+
const ui = await ensureCreateLauncherModal();
|
|
681
|
+
if (!ui) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
applyDefaultsToUi(ui, defaults);
|
|
686
|
+
ui.templateManager.syncTemplateFields(ui.promptTextarea.value, defaults.templateValues || {});
|
|
687
|
+
|
|
688
|
+
requestAnimationFrame(() => {
|
|
689
|
+
ui.overlay.classList.add('is-visible');
|
|
690
|
+
requestAnimationFrame(() => {
|
|
691
|
+
ui.folderInput.select();
|
|
692
|
+
ui.promptTextarea.focus();
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
modalKeydownHandler = (event) => {
|
|
697
|
+
if (event.key === 'Escape') {
|
|
698
|
+
event.preventDefault();
|
|
699
|
+
hideModal();
|
|
700
|
+
} else if (event.key === 'Enter' && event.target === ui.folderInput) {
|
|
701
|
+
event.preventDefault();
|
|
702
|
+
submitFromUi(ui);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
document.addEventListener('keydown', modalKeydownHandler, true);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function hideModal() {
|
|
710
|
+
if (!modalInstance) return;
|
|
711
|
+
modalInstance.overlay.classList.remove('is-visible');
|
|
712
|
+
if (modalKeydownHandler) {
|
|
713
|
+
document.removeEventListener('keydown', modalKeydownHandler, true);
|
|
714
|
+
modalKeydownHandler = null;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async function mountPage(root, defaults = {}) {
|
|
719
|
+
if (!root) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const tools = await getCreateLauncherTools();
|
|
723
|
+
const ui = buildCreateLauncherUI({ mode: 'page', tools });
|
|
724
|
+
|
|
725
|
+
root.innerHTML = '';
|
|
726
|
+
root.appendChild(ui.container);
|
|
727
|
+
|
|
728
|
+
ui.confirmButton.addEventListener('click', () => submitFromUi(ui));
|
|
729
|
+
|
|
730
|
+
applyDefaultsToUi(ui, defaults);
|
|
731
|
+
ui.templateManager.syncTemplateFields(ui.promptTextarea.value, defaults.templateValues || {});
|
|
732
|
+
|
|
733
|
+
requestAnimationFrame(() => {
|
|
734
|
+
ui.promptTextarea.focus();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
return ui;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
window.CreateLauncher = {
|
|
741
|
+
showModal,
|
|
742
|
+
hideModal,
|
|
743
|
+
ensureModalReady: ensureCreateLauncherModal,
|
|
744
|
+
mountPage,
|
|
745
|
+
applyTemplateValues,
|
|
746
|
+
generateFolderSuggestion,
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
window.dispatchEvent(new CustomEvent('CreateLauncherReady'));
|
|
751
|
+
} catch (_) {
|
|
752
|
+
// ignore if CustomEvent is unavailable
|
|
753
|
+
}
|
|
754
|
+
})(window, document);
|