import init, {
init_cli,
run_command_async,
get_completions,
get_history_item,
search_history
} from '/cli-ui/forge_cli_wasm.js';
async function main() {
const term = new Terminal({
cursorBlink: true,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
},
cols: 120, scrollback: 1000,
convertEol: true, });
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();
window.addEventListener('resize', () => fitAddon.fit());
term.writeln('Welcome to the Forge CLI (WASM Interface)');
term.writeln('------------------------------------------');
term.writeln('');
try {
term.write('Loading WASM module...');
await init();
term.writeln('\r✅ WASM module loaded successfully.');
const baseUrl = window.location.origin; term.write(`Fetching OpenAPI spec from ${baseUrl}/api-docs/openapi.json...`);
const response = await fetch(`${baseUrl}/api-docs/openapi.json`);
if (!response.ok) {
throw new Error(`Failed to fetch spec: ${response.statusText}`);
}
const specJson = await response.text();
const spec = JSON.parse(specJson);
window.__openapiSpec = spec;
window.__baseUrl = baseUrl;
init_cli(specJson, baseUrl);
term.writeln('\r✅ CLI initialized with OpenAPI spec.');
} catch (e) {
term.writeln(`\r\n❌ Error during initialization: ${e}`);
return;
}
let currentLine = '';
let cursorPosition = 0; let historyIndex = -1; let isInReverseSearch = false;
let reverseSearchQuery = '';
let completionMenu = null;
const prompt = '\r\n$ ';
const promptOnly = '$ ';
function redrawLine() {
term.write('\r' + promptOnly);
term.write('\x1b[K');
if (isInReverseSearch) {
term.write('\r\x1b[K'); term.write(`(reverse-i-search)'${reverseSearchQuery}': ${currentLine}`);
} else {
term.write(currentLine);
}
if (cursorPosition < currentLine.length) {
const moveCursor = currentLine.length - cursorPosition;
term.write('\x1b[' + moveCursor + 'D'); }
}
function insertChar(char) {
currentLine = currentLine.slice(0, cursorPosition) + char + currentLine.slice(cursorPosition);
cursorPosition++;
redrawLine();
}
function deleteChar() {
if (cursorPosition > 0) {
currentLine = currentLine.slice(0, cursorPosition - 1) + currentLine.slice(cursorPosition);
cursorPosition--;
redrawLine();
}
}
function moveCursor(direction) {
if (direction === 'left' && cursorPosition > 0) {
cursorPosition--;
term.write('\x1b[D');
} else if (direction === 'right' && cursorPosition < currentLine.length) {
cursorPosition++;
term.write('\x1b[C');
}
}
function handleTabCompletion() {
try {
const completionResult = get_completions(currentLine, cursorPosition);
const suggestions = JSON.parse(completionResult.suggestions);
if (suggestions.length === 0) {
return;
}
if (suggestions.length === 1) {
const suggestion = suggestions[0];
const beforeCursor = currentLine.slice(0, suggestion.start_pos);
const afterCursor = currentLine.slice(suggestion.end_pos);
currentLine = beforeCursor + suggestion.value + afterCursor;
cursorPosition = beforeCursor.length + suggestion.value.length;
redrawLine();
} else {
term.writeln('');
suggestions.slice(0, 10).forEach(suggestion => {
const desc = suggestion.description ? ` - ${suggestion.description}` : '';
term.writeln(` ${suggestion.value}${desc}`);
});
redrawLine();
}
} catch (e) {
console.error('Tab completion error:', e);
}
}
function navigateHistory(direction) {
if (direction === 'up') {
const item = get_history_item(historyIndex + 1);
if (item) {
historyIndex++;
currentLine = item;
cursorPosition = currentLine.length;
redrawLine();
}
} else if (direction === 'down') {
if (historyIndex > 0) {
historyIndex--;
const item = get_history_item(historyIndex);
if (item) {
currentLine = item;
cursorPosition = currentLine.length;
redrawLine();
}
} else if (historyIndex === 0) {
historyIndex = -1;
currentLine = '';
cursorPosition = 0;
redrawLine();
}
}
}
function handleReverseSearch(char) {
if (char) {
reverseSearchQuery += char;
}
try {
const searchResults = JSON.parse(search_history(reverseSearchQuery));
if (searchResults.length > 0) {
currentLine = searchResults[0];
cursorPosition = currentLine.length;
}
redrawLine();
} catch (e) {
console.error('Reverse search error:', e);
}
}
function exitReverseSearch() {
isInReverseSearch = false;
reverseSearchQuery = '';
cursorPosition = currentLine.length;
redrawLine();
}
async function executeCommandJS(commandLine) {
try {
const spec = window.__openapiSpec;
const baseUrl = window.__baseUrl || '';
if (!spec) return 'Error: OpenAPI spec not loaded.';
const tokens = commandLine.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
if (tokens.length === 0) return '';
const cmd = tokens[0];
const args = {};
for (let i = 1; i < tokens.length; i++) {
const t = tokens[i];
if (t.startsWith('--')) {
const key = t.replace(/^--/, '');
const val = (i + 1 < tokens.length && !tokens[i + 1].startsWith('--')) ? tokens[++i] : '';
args[key] = val.replace(/^"|"$/g, '');
}
}
const parts = cmd.split('.');
const method = parts.pop().toUpperCase();
const cmdSegs = parts;
let matched = null;
for (const [key, item] of Object.entries(spec.paths || {})) {
const keySegs = key.split('/').filter(s => s);
if (keySegs.length !== cmdSegs.length) continue;
let ok = true;
for (let i = 0; i < keySegs.length; i++) {
const ks = keySegs[i];
const cs = cmdSegs[i];
const isParam = ks.startsWith('{') && ks.endsWith('}');
if (!isParam && ks !== cs) { ok = false; break; }
}
if (ok) { matched = [key, item]; break; }
}
if (!matched) {
return `API request failed (JS fallback): Path not found for /${cmdSegs.join('/')}`;
}
const [pathTemplate, pathItem] = matched;
const op = (pathItem[method.toLowerCase()]);
if (!op) return `API request failed (JS fallback): Operation not found for ${cmd}`;
let finalPath = pathTemplate;
const used = new Set();
if (Array.isArray(op.parameters)) {
for (const p of op.parameters) {
const prm = p && p.name ? p : (p && p.$ref ? null : null);
if (!prm) continue;
if (p.in === 'path' && args[p.name] != null) {
finalPath = finalPath.replace(`{${p.name}}`, encodeURIComponent(args[p.name]));
used.add(p.name);
}
}
}
const query = [];
for (const [k, v] of Object.entries(args)) {
if (!used.has(k)) query.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`);
}
let serverUrl = '';
if (Array.isArray(spec.servers) && spec.servers.length > 0 && spec.servers[0].url) {
serverUrl = spec.servers[0].url;
}
const url = `${baseUrl}${serverUrl}${finalPath}${query.length ? ('?' + query.join('&')) : ''}`;
const resp = await fetch(url, { method });
const text = await resp.text();
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch {
return text;
}
} catch (e) {
return `API request failed (JS fallback): ${e}`;
}
}
term.write(prompt);
term.onKey(({ key, domEvent }) => {
const { keyCode, ctrlKey, altKey, metaKey } = domEvent;
if (ctrlKey && keyCode === 82 && !isInReverseSearch) {
isInReverseSearch = true;
reverseSearchQuery = '';
currentLine = '';
cursorPosition = 0;
redrawLine();
return;
}
if (isInReverseSearch) {
if (keyCode === 13) { exitReverseSearch();
return;
} else if (keyCode === 27) { isInReverseSearch = false;
reverseSearchQuery = '';
currentLine = '';
cursorPosition = 0;
redrawLine();
return;
} else if (keyCode === 8) { if (reverseSearchQuery.length > 0) {
reverseSearchQuery = reverseSearchQuery.slice(0, -1);
handleReverseSearch();
}
return;
} else if (!ctrlKey && !altKey && !metaKey && key.length === 1) {
handleReverseSearch(key);
return;
}
return;
}
if (keyCode === 13) { if (currentLine.trim()) {
term.writeln('');
(async () => {
try {
let result = await run_command_async(currentLine);
const plain = String(result);
if (plain.includes('Path not found for')) {
result = await executeCommandJS(currentLine);
}
const cleanResult = String(result)
.replace(/\x1b\[[0-9;]*m/g, '')
.replace(/\x1b\[[0-9]*[A-Za-z]/g, '')
.replace(/\[\d+m/g, '');
const lines = cleanResult.split('\n');
lines.forEach((line, index) => {
if (index === lines.length - 1 && line === '') {
return;
}
term.writeln(line);
});
} catch (error) {
term.writeln(`Error: ${error}`);
}
term.write(prompt);
})();
currentLine = '';
cursorPosition = 0;
historyIndex = -1;
} else {
term.write(prompt);
}
} else if (keyCode === 9) { domEvent.preventDefault();
handleTabCompletion();
} else if (keyCode === 8) { deleteChar();
} else if (keyCode === 37) { moveCursor('left');
} else if (keyCode === 39) { moveCursor('right');
} else if (keyCode === 38) { navigateHistory('up');
} else if (keyCode === 40) { navigateHistory('down');
} else if (keyCode === 36) { cursorPosition = 0;
redrawLine();
} else if (keyCode === 35) { cursorPosition = currentLine.length;
redrawLine();
} else if (!ctrlKey && !altKey && !metaKey && key.length === 1) {
insertChar(key);
}
});
}
main();