huggingbot-modules / modules_huggingface.js
broadfield's picture
Add full expanded HuggingFace Hub module v2.0.0
3bc0be8 verified
Raw History Blame Contribute Delete
52.7 kB
// HuggingBot Module: HuggingFace Hub v2.0.0 (FULL + EXPANDED)
// ============================================================================
// Full remote control of the HuggingFace Hub from chat:
// auth, Spaces CRUD, file ops, secrets/hardware/sleep config, search,
// model inference (Inference API), Gradio Space API, repo browsing.
// Rebuilt for the HuggingBot ModuleSystem from ChatSeed v12 (all API fixes
// applied: flavor/gcTimeout keys, PUT /settings, paused-space delete pre-check,
// deep hardware name extraction) and expanded with:
// * hf_search_datasets
// * modern Gradio API routing (/api/{name} with /api/predict fallback)
// * pipeline-aware Inference API calls (task routing)
// * binary (image) inference response handling -> inline markdown
// * multi-repo-type support for file/repo operations (space|model|dataset)
// ============================================================================
(function(MS) {
'use strict';
var HF_API_BASE = 'https://huggingface.co';
var INFER_API_BASE = 'https://api-inference.huggingface.co';
var TOKEN_KEY = 'chatseed_hf_token';
// ─── Token helpers ────────────────────────────────────────────────────
function getToken() { try { return localStorage.getItem(TOKEN_KEY) || ''; } catch (e) { return ''; } }
function setToken(t) { try { localStorage.setItem(TOKEN_KEY, t); } catch (e) {} }
function clearToken() { try { localStorage.removeItem(TOKEN_KEY); } catch (e) {} }
function authHeader() {
var t = getToken();
if (!t) throw new Error('No HuggingFace token set. Use hf_set_token or hf_login_ui to set your HF access token first.');
return { 'Authorization': 'Bearer ' + t };
}
// ─── Fetch helpers ────────────────────────────────────────────────────
async function hfFetch(path, options) {
options = options || {};
var headers = Object.assign({ 'Content-Type': 'application/json' }, authHeader(), options.headers || {});
var res = await fetch(HF_API_BASE + path, Object.assign({}, options, { headers: headers }));
if (!res.ok) {
var msg = '';
try { var err = await res.json(); msg = err.error || JSON.stringify(err); }
catch (e) { try { msg = await res.text(); } catch (e2) { msg = res.statusText; } }
throw new Error('HF API ' + res.status + ': ' + msg);
}
if (res.status === 204) return null;
return (res.headers.get('content-type') || '').indexOf('application/json') > -1 ? res.json() : res.text();
}
async function hfFetchRaw(path) {
var token = getToken();
var headers = {};
if (token) headers['Authorization'] = 'Bearer ' + token;
var res = await fetch(HF_API_BASE + path, { headers: headers });
if (!res.ok) {
var msg = '';
try { var err = await res.json(); msg = err.error || JSON.stringify(err); } catch (e) { msg = res.statusText; }
throw new Error('HF API ' + res.status + ': ' + msg);
}
return res.text();
}
async function hfFetchHead(path) {
var token = getToken();
var headers = {};
if (token) headers['Authorization'] = 'Bearer ' + token;
var res = await fetch(HF_API_BASE + path, { method: 'HEAD', headers: headers });
return { ok: res.ok, status: res.status };
}
// ─── Repo helpers ─────────────────────────────────────────────────────
function repoTypePath(type) {
switch (type) {
case 'model': return 'models';
case 'dataset': return 'datasets';
case 'space': return 'spaces';
default: return 'spaces';
}
}
function parseRepoId(repoId) {
var parts = String(repoId || '').split('/');
if (parts.length !== 2) throw new Error('Invalid repo_id. Use format "namespace/repo-name"');
return { namespace: parts[0], name: parts[1] };
}
async function commitFiles(repoType, namespace, repo, files, deletedEntries, summary, description) {
var body = {
summary: summary || 'Update via HuggingBot',
description: description || '',
files: (files || []).map(function (f) {
return { path: f.path, content: f.content, encoding: f.encoding || 'utf-8' };
})
};
if (deletedEntries && deletedEntries.length) body.deletedEntries = deletedEntries;
return hfFetch('/api/' + repoTypePath(repoType) + '/' + namespace + '/' + repo + '/commit/main', {
method: 'POST',
body: JSON.stringify(body)
});
}
// Centralized settings helper β€” routes ALL Space settings through
// PUT /api/spaces/{ns}/{name}/settings with the keys HF actually accepts.
async function updateSpaceSettings(namespace, repo, settings) {
return hfFetch('/api/spaces/' + namespace + '/' + repo + '/settings', {
method: 'PUT',
body: JSON.stringify(settings)
});
}
// Deep-extract a human-readable hardware name from varied API response shapes.
function extractHardwareName(hw) {
if (!hw) return 'cpu-basic';
if (typeof hw === 'string') return hw;
if (typeof hw === 'object') {
return hw.currentPrettyName
|| hw.requestedPrettyName
|| (hw.current ? (typeof hw.current === 'object' ? (hw.current.id || hw.current.name) : hw.current) : null)
|| (hw.requested ? (typeof hw.requested === 'object' ? (hw.requested.id || hw.requested.name) : hw.requested) : null)
|| hw.id || hw.name || hw.displayName
|| JSON.stringify(hw) || 'cpu-basic';
}
return String(hw);
}
function repoAppUrl(repoId) {
return 'https://' + repoId.replace(/\//g, '-') + '.hf.space';
}
function maskToken(t) {
return t.substring(0, 6) + '…' + t.substring(t.length - 4);
}
function parseJsonArg(v) {
if (!v) return {};
if (typeof v === 'object') return v;
try { return JSON.parse(v); } catch (e) { throw new Error('Invalid JSON argument: ' + v); }
}
// ─── Secure Login Popup (token never passes through the model) ────────
function openLoginPopup() {
var existing = document.getElementById('huggingbot-hf-login-overlay');
if (existing) existing.remove();
var overlay = document.createElement('div');
overlay.id = 'huggingbot-hf-login-overlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.7);z-index:10000;display:flex;align-items:center;justify-content:center;padding:16px;';
var box = document.createElement('div');
box.style.cssText = 'background:#111827;border:1px solid #374151;border-radius:16px;padding:24px;width:420px;max-width:100%;box-shadow:0 25px 50px rgba(0,0,0,.5);font-family:system-ui,sans-serif;';
var header = document.createElement('div');
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;';
header.innerHTML = '<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:1.4rem;">πŸ€—</span><span style="font-size:1.05rem;font-weight:600;color:#f3f4f6;">HuggingFace Login</span></div>' +
'<button id="huggingbot-hf-close" style="background:none;border:none;color:#9ca3af;font-size:1.2rem;cursor:pointer;">βœ•</button>';
var body = document.createElement('div');
body.innerHTML =
'<p style="color:#d1d5db;font-size:.82rem;line-height:1.5;margin:0 0 14px;">Paste your HuggingFace access token.</p>' +
'<input id="huggingbot-hf-token" type="password" placeholder="hf_..." style="width:100%;box-sizing:border-box;padding:10px 12px;background:#030712;border:1px solid #4b5563;border-radius:8px;color:#f3f4f6;font-size:.9rem;outline:none;"/>' +
'<div style="margin-top:6px;display:flex;align-items:center;gap:6px;"><input id="huggingbot-hf-show" type="checkbox" style="accent-color:#059669;"><label for="huggingbot-hf-show" style="color:#6b7280;font-size:.75rem;cursor:pointer;">Show token</label></div>' +
'<div id="huggingbot-hf-status" style="display:none;font-size:.8rem;padding:8px 12px;border-radius:8px;margin-top:10px;"></div>' +
'<div style="display:flex;gap:10px;margin-top:14px;">' +
'<button id="huggingbot-hf-submit" style="flex:1;padding:10px;background:#059669;color:#fff;border:none;border-radius:10px;font-weight:500;cursor:pointer;">πŸ” Set Token</button>' +
'<button id="huggingbot-hf-cancel" style="padding:10px 16px;background:#374151;color:#d1d5db;border:none;border-radius:10px;cursor:pointer;">Cancel</button></div>' +
'<p style="color:#6b7280;font-size:.7rem;margin:12px 0 0;text-align:center;">Get a token at <a href="https://huggingface.co/settings/tokens" target="_blank" style="color:#60a5fa;">huggingface.co/settings/tokens</a></p>';
box.appendChild(header);
box.appendChild(body);
overlay.appendChild(box);
document.body.appendChild(overlay);
var input = document.getElementById('huggingbot-hf-token');
var statusEl = document.getElementById('huggingbot-hf-status');
var submitBtn = document.getElementById('huggingbot-hf-submit');
function close() { overlay.remove(); }
function showStatus(msg, color) {
statusEl.style.display = 'block';
statusEl.style.background = color + '18';
statusEl.style.border = '1px solid ' + color + '44';
statusEl.style.color = color;
statusEl.innerHTML = msg;
}
document.getElementById('huggingbot-hf-show').addEventListener('change', function () { input.type = this.checked ? 'text' : 'password'; });
document.getElementById('huggingbot-hf-close').addEventListener('click', close);
document.getElementById('huggingbot-hf-cancel').addEventListener('click', close);
submitBtn.addEventListener('click', async function () {
var token = input.value.trim();
if (!token) return showStatus('⚠️ Please enter a token.', '#f59e0b');
if (token.indexOf('hf_') !== 0) return showStatus('⚠️ Tokens should start with <code>hf_</code>.', '#f59e0b');
setToken(token);
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Verifying...';
try {
var res = await fetch('https://huggingface.co/api/whoami-v2', { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } });
if (res.ok) {
var info = await res.json();
showStatus('βœ… Authenticated as <strong>' + (info.name || info.user || 'unknown') + '</strong> (' + maskToken(token) + ')', '#059669');
setTimeout(close, 1200);
} else {
showStatus('⚠️ Token stored, but verification failed (' + res.status + ').', '#f59e0b');
submitBtn.disabled = false;
submitBtn.textContent = 'πŸ” Set Token';
}
} catch (e) {
showStatus('⚠️ Token stored locally, but could not verify: ' + e.message, '#f59e0b');
submitBtn.disabled = false;
submitBtn.textContent = 'πŸ” Set Token';
}
});
input.addEventListener('keydown', function (e) { if (e.key === 'Enter') submitBtn.click(); if (e.key === 'Escape') close(); });
setTimeout(function () { input.focus(); }, 100);
}
// ─── Tool schemas (exactly as registered in the runtime) ──────────────
var TOOLS = [
{ name: 'hf_set_token', description: 'Store your HuggingFace access token for this session. Get it from https://huggingface.co/settings/tokens', parameters: { type: 'object', properties: { token: { type: 'string', description: 'Your HF access token (hf_...). Required for all write operations.' } }, required: ['token'] } },
{ name: 'hf_login_ui', description: 'πŸ” Open a secure popup to enter your HF token. The token is stored directly in your browser β€” it never passes through the AI model.', parameters: { type: 'object', properties: {} } },
{ name: 'hf_logout', description: 'Clear your stored HuggingFace token from localStorage.', parameters: { type: 'object', properties: {} } },
{ name: 'hf_token_status', description: 'Check if a HF token is stored and who it belongs to (without revealing the token).', parameters: { type: 'object', properties: {} } },
{ name: 'hf_whoami', description: 'Check who you are authenticated as on HuggingFace Hub.', parameters: { type: 'object', properties: {} } },
{ name: 'hf_create_space', description: 'Create a new HuggingFace Space. Supports Gradio, Streamlit, Docker, or static SDKs.', parameters: { type: 'object', properties: { name: { type: 'string', description: 'Space name (e.g. "my-cool-demo"). Must be unique under your namespace.' }, sdk: { type: 'string', enum: ['gradio', 'streamlit', 'docker', 'static'], default: 'gradio', description: 'SDK/framework to use' }, type: { type: 'string', enum: ['space'], default: 'space' }, title: { type: 'string', description: 'Optional display title' }, license: { type: 'string', description: 'Optional license (e.g. "mit", "apache-2.0")' }, hardware: { type: 'string', description: 'Hardware upgrade (e.g. "t4-medium", "cpu-upgrade"). See HF docs for options.' }, secrets: { type: 'string', description: 'JSON object of secrets {"key": "value", ...}' }, variables: { type: 'string', description: 'JSON object of env variables {"KEY": "VALUE", ...}' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds (for GPU spaces)' }, private: { type: 'boolean', default: false, description: 'Create as private space?' }, org: { type: 'string', description: 'Organization namespace (if creating under an org)' } }, required: ['name'] } },
{ name: 'hf_duplicate_space', description: 'Duplicate an existing Space (useful for forking with custom config).', parameters: { type: 'object', properties: { from: { type: 'string', description: 'Source Space ID (e.g. "username/space-name")' }, name: { type: 'string', description: 'New Space name' }, org: { type: 'string', description: 'Optional org to duplicate into' }, hardware: { type: 'string', description: 'Hardware for the new Space' }, secrets: { type: 'string', description: 'JSON of secrets' }, variables: { type: 'string', description: 'JSON of env variables' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds' }, private: { type: 'boolean', default: false } }, required: ['from', 'name'] } },
{ name: 'hf_delete_space', description: 'Delete a Space repository from HuggingFace Hub.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to delete (e.g. "username/my-space")' } }, required: ['repo_id'] } },
{ name: 'hf_upload_file', description: 'Upload a single file to a Space (app.py, requirements.txt, README.md, etc.). Triggers a rebuild.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'Path in the repo (e.g. "app.py" or "src/utils.py")' }, content: { type: 'string', description: 'File content as text' }, summary: { type: 'string', description: 'Commit message (default: "Update via HuggingBot")' }, description: { type: 'string', description: 'Optional commit description' } }, required: ['repo_id', 'path', 'content'] } },
{ name: 'hf_upload_app', description: 'Upload a complete Gradio/Streamlit app with requirements.txt in one go.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, app_code: { type: 'string', description: 'The app.py (or app.js for static) source code' }, requirements: { type: 'string', description: 'Optional requirements.txt content (one package per line)' }, readme: { type: 'string', description: 'Optional README.md content' }, extra_files: { type: 'string', description: 'Optional JSON of extra files {"path": "content", ...}' }, summary: { type: 'string', description: 'Commit message' } }, required: ['repo_id', 'app_code'] } },
{ name: 'hf_delete_file', description: 'Delete a file from a Space repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path in the repo to delete' }, summary: { type: 'string', description: 'Commit message' } }, required: ['repo_id', 'path'] } },
{ name: 'hf_set_secret', description: 'Add or update a secret (environment variable) for a Space. Triggers a restart.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' }, key: { type: 'string', description: 'Secret name (e.g. "HF_TOKEN", "API_KEY")' }, value: { type: 'string', description: 'Secret value' } }, required: ['repo_id', 'key', 'value'] } },
{ name: 'hf_delete_secret', description: 'Delete a secret from a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, key: { type: 'string', description: 'Secret name to delete' } }, required: ['repo_id', 'key'] } },
{ name: 'hf_set_hardware', description: 'Request a hardware upgrade/downgrade for a Space (e.g. CPU β†’ GPU). Triggers a restart.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, hardware: { type: 'string', description: 'Hardware tier: cpu-basic (free), cpu-upgrade ($0.03/hr), t4-small ($0.40), t4-medium ($0.60), l4-1x ($0.80), a10g-small ($1.00), a100-large ($2.50)', enum: ['cpu-basic', 'cpu-upgrade', 't4-small', 't4-medium', 'l4-1x', 'a10g-small', 'a10g-large', 'a100-large'] }, sleep_time: { type: 'number', description: 'Auto-sleep timeout in seconds (for GPU) - e.g. 7200 = 2h' } }, required: ['repo_id', 'hardware'] } },
{ name: 'hf_set_visibility', description: 'Change the visibility of a Space (public / private).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, visibility: { type: 'string', enum: ['public', 'private'], description: 'New visibility level' } }, required: ['repo_id', 'visibility'] } },
{ name: 'hf_set_sleep', description: 'Set auto-sleep timeout for a Space (useful for GPU spaces to save cost).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' }, sleep_time: { type: 'number', description: 'Sleep timeout in seconds (0 to disable, 300 = 5 min, 3600 = 1h, 7200 = 2h)' } }, required: ['repo_id', 'sleep_time'] } },
{ name: 'hf_pause_space', description: 'Pause a Space to stop billing. It can be restarted later with hf_restart_space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to pause' } }, required: ['repo_id'] } },
{ name: 'hf_restart_space', description: 'Restart a paused Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID to restart' } }, required: ['repo_id'] } },
{ name: 'hf_space_status', description: 'Get the runtime status, hardware, and stage of a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID (e.g. "username/my-space")' } }, required: ['repo_id'] } },
{ name: 'hf_space_logs', description: 'Fetch the latest build/runtime logs from a Space.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Space ID' } }, required: ['repo_id'] } },
{ name: 'hf_search_spaces', description: 'Search for Spaces on the HuggingFace Hub by query.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results (1-25)' } }, required: ['query'] } },
{ name: 'hf_list_my_spaces', description: 'List all Spaces owned by your account.', parameters: { type: 'object', properties: { limit: { type: 'number', default: 10, description: 'Max results' } }, required: [] } },
{ name: 'hf_search_models', description: 'Search for models on the HuggingFace Hub.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results' } }, required: ['query'] } },
{ name: 'hf_call_space', description: 'Call a Gradio Space API endpoint (make predictions/inference via the Space API).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Gradio Space ID (e.g. "abidlabs/en2fr")' }, api_name: { type: 'string', default: '/predict', description: 'API endpoint name (e.g. "/predict", "/generate")' }, data: { type: 'string', description: 'JSON array of input data (e.g. ["Hello world"])' } }, required: ['repo_id', 'data'] } },
{ name: 'hf_inference', description: 'Run inference on a HuggingFace model using the free Inference API.', parameters: { type: 'object', properties: { model: { type: 'string', description: 'Model ID (e.g. "gpt2", "meta-llama/Llama-3.2-3B-Instruct")' }, inputs: { type: 'string', description: 'Input text or JSON string of inputs' }, parameters: { type: 'string', description: 'Optional JSON string of generation parameters (max_length, temperature, etc.)' }, task: { type: 'string', description: 'Optional task type (e.g. "text-generation", "image-classification", "automatic-speech-recognition")' } }, required: ['model', 'inputs'] } },
{ name: 'hf_repo_info', description: 'Get detailed information about any HF repo (Space, Model, or Dataset).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/repo-name")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id'] } },
{ name: 'hf_list_files', description: 'List files in a repository at a given path.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' }, path: { type: 'string', default: '', description: 'Subdirectory path (optional)' }, revision: { type: 'string', default: 'main', description: 'Branch/revision' } }, required: ['repo_id'] } },
{ name: 'hf_read_file', description: 'Read the content of a file from ANY HuggingFace repo (Space, Model, or Dataset), including PRIVATE repos.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path in the repo (e.g. "app.py", "config.json")' }, revision: { type: 'string', default: 'main', description: 'Branch/commit revision' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id', 'path'] } },
{ name: 'hf_file_exists', description: 'Check if a file exists in a HuggingFace repo (including private repos).', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, path: { type: 'string', description: 'File path to check (e.g. "app.py", "config.json")' }, revision: { type: 'string', default: 'main', description: 'Branch/revision' } }, required: ['repo_id', 'path'] } },
{ name: 'hf_repo_exists', description: 'Check if a HuggingFace repository exists (Space, Model, or Dataset). Works for private repos too.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id'] } },
{ name: 'hf_list_commits', description: 'View the commit history of a HuggingFace repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' }, revision: { type: 'string', default: 'main', description: 'Branch/revision to view commits for' }, limit: { type: 'number', default: 10, description: 'Max commits to show (1-50)' } }, required: ['repo_id'] } },
{ name: 'hf_move_file', description: 'Rename or move a file in a HuggingFace repository.', parameters: { type: 'object', properties: { repo_id: { type: 'string', description: 'Repo ID (e.g. "username/my-space")' }, source_path: { type: 'string', description: 'Current file path (e.g. "old_name.py")' }, dest_path: { type: 'string', description: 'New file path (e.g. "new_name.py" or "subfolder/new_name.py")' }, summary: { type: 'string', description: 'Commit message (default: "Move/Rename file")' }, type: { type: 'string', enum: ['space', 'model', 'dataset'], default: 'space', description: 'Repo type' } }, required: ['repo_id', 'source_path', 'dest_path'] } },
// ── Expanded (new in v2.0.0) ────────────────────────────────────────
{ name: 'hf_search_datasets', description: 'Search for datasets on the HuggingFace Hub.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 5, description: 'Max results (1-25)' } }, required: ['query'] } }
];
// ─── Tool implementations ─────────────────────────────────────────────
var REGISTRY = {
hf_set_token: async function (a) {
var t = String(a.token || '').trim();
if (!t || t.indexOf('hf_') !== 0) return '⚠️ Invalid token format. Tokens start with `hf_`. Get one at https://huggingface.co/settings/tokens';
setToken(t);
try {
var w = await hfFetch('/api/whoami-v2');
return 'βœ… **HuggingFace token stored** (' + maskToken(t) + ')\nπŸ‘€ Authenticated as: **' + (w.name || w.user || 'unknown') + '**\nYou can now use all HF tools.';
} catch (e) {
return '⚠️ Token stored (' + maskToken(t) + ') but verification failed: ' + e.message + '. Check that the token is valid.';
}
},
hf_login_ui: async function () {
openLoginPopup();
return 'πŸ” **Secure Login Popup Opened**\n\nA popup has been opened where you can paste your HuggingFace token.\n\n> πŸ›‘οΈ Your token is stored directly in your browser\'s localStorage β€” it **never passes through this AI model**.\n\nπŸ’‘ Tip: you can also paste your token directly in chat with `hf_set_token`, but the popup is more secure.\n\nπŸ“‹ Get a token at: https://huggingface.co/settings/tokens';
},
hf_logout: async function () {
var had = !!getToken();
clearToken();
return had ? 'πŸšͺ **Logged out.** HuggingFace token removed from localStorage.' : 'πŸ‘‹ No token was stored. Nothing to clear.';
},
hf_token_status: async function () {
var t = getToken();
if (!t) return 'πŸ”‘ **No token stored.** Use `hf_login_ui` (secure popup) or `hf_set_token` to log in.';
try {
var info = await hfFetch('/api/whoami-v2');
return ['πŸ”‘ **Token Status:** Active (' + maskToken(t) + ')', 'πŸ‘€ **Authenticated as:** ' + (info.name || info.user || 'unknown'), 'πŸ“§ **Email:** ' + (info.email || 'N/A'), 'πŸ’³ **Can Pay:** ' + (info.canPay ? 'Yes' : 'No'), '', 'πŸ’‘ Use `hf_logout` to clear your token.'].join('\n');
} catch (e) {
return '⚠️ **Token stored** (' + maskToken(t) + ') but verification failed: ' + e.message;
}
},
hf_whoami: async function () {
try {
var info = await hfFetch('/api/whoami-v2');
var lines = ['**πŸ‘€ HuggingFace User Info**', '**ID:** ' + (info.id || info.name || 'N/A'), '**Name:** ' + (info.name || info.user || 'N/A'), '**Email:** ' + (info.email || 'N/A'), '**Type:** ' + (info.type || info.org || 'user'), '**Can Pay:** ' + (info.canPay ? 'βœ… Yes' : '❌ No'), '**Plan:** ' + (info.plan ? (info.plan.name || info.plan) : 'Free/Unknown')];
if (info.orgs && info.orgs.length) lines.push('**Organizations:** ' + info.orgs.map(function (o) { return o.name; }).join(', '));
if (info.usage && info.usage.inference) lines.push('**Inference Usage:** ' + JSON.stringify(info.usage.inference));
return lines.join('\n');
} catch (e) { return '❌ Failed to get user info: ' + e.message; }
},
hf_create_space: async function (a) {
var me = await hfFetch('/api/whoami-v2');
var ns = a.org || me.name;
var payload = { name: a.name, type: 'space', sdk: a.sdk || 'gradio', private: !!a.private };
if (a.title) payload.title = a.title;
if (a.license) payload.license = a.license;
if (a.hardware) payload.hardware = a.hardware;
if (a.sleep_time) payload.sleep_time = a.sleep_time;
var secrets = parseJsonArg(a.secrets);
var variables = parseJsonArg(a.variables);
if (Object.keys(secrets).length) payload.secrets = secrets;
if (Object.keys(variables).length) payload.variables = variables;
await hfFetch('/api/repos/create', { method: 'POST', body: JSON.stringify(payload) });
var repoId = ns + '/' + a.name;
var out = ['βœ… **Space created successfully!**', '', '**Repo:** ' + repoId, '**SDK:** ' + (a.sdk || 'gradio'), '**Visibility:** ' + (a.private ? 'πŸ”’ Private' : '🌍 Public'), '**URL:** https://huggingface.co/spaces/' + repoId, '**App:** ' + repoAppUrl(repoId)];
if (a.hardware) out.push('⚑ Hardware requested: ' + a.hardware);
if (a.sleep_time) out.push('πŸ’€ Sleep timeout: ' + a.sleep_time + 's');
out.push('', 'πŸ’‘ **Next steps:** Upload files using `hf_upload_file` or `hf_upload_app`');
return out.join('\n');
},
hf_duplicate_space: async function (a) {
var me = await hfFetch('/api/whoami-v2');
var ns = a.org || me.name;
var body = { from: a.from, name: a.name, namespace: ns, private: !!a.private };
if (a.hardware) body.hardware = a.hardware;
if (a.sleep_time) body.sleep_time = a.sleep_time;
var secrets = parseJsonArg(a.secrets);
var variables = parseJsonArg(a.variables);
if (Object.keys(secrets).length) body.secrets = secrets;
if (Object.keys(variables).length) body.variables = variables;
await hfFetch('/api/spaces/duplicate', { method: 'POST', body: JSON.stringify(body) });
var repoId = ns + '/' + a.name;
var out = ['βœ… **Space duplicated!**', '', '**From:** ' + a.from, '**New Repo:** ' + repoId, '**URL:** https://huggingface.co/spaces/' + repoId, '**App:** ' + repoAppUrl(repoId)];
if (a.hardware) out.push('**Hardware:** ' + a.hardware);
if (a.sleep_time) out.push('**Sleep:** ' + a.sleep_time + 's');
return out.join('\n');
},
hf_delete_space: async function (a) {
var p = parseRepoId(a.repo_id);
try {
var rt = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime');
if (rt.stage === 'PAUSED' || rt.stage === 'SLEEPING') {
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/restart', { method: 'POST' });
await new Promise(function (r) { setTimeout(r, 2000); });
}
} catch (e) { /* best-effort */ }
try {
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name, { method: 'DELETE' });
return 'βœ… **Space deleted:** ' + a.repo_id;
} catch (e) {
try {
await hfFetch('/api/repos/' + p.namespace + '/' + p.name, { method: 'DELETE' });
return 'βœ… **Space deleted (via repos endpoint):** ' + a.repo_id;
} catch (e2) {
throw new Error('Could not delete space: ' + e.message + '. Try restarting it first with hf_restart_space.');
}
}
},
hf_upload_file: async function (a) {
var p = parseRepoId(a.repo_id);
await commitFiles('space', p.namespace, p.name, [{ path: a.path, content: a.content }], null, a.summary || 'Update via HuggingBot', a.description || '');
return ['βœ… **File uploaded:** `' + a.path + '` β†’ `' + a.repo_id + '`', 'πŸ“ ' + (a.summary || 'Update via HuggingBot'), '', 'πŸ’‘ The Space will now rebuild automatically. Check status with `hf_space_status`.'].join('\n');
},
hf_upload_app: async function (a) {
var p = parseRepoId(a.repo_id);
var files = [{ path: 'app.py', content: a.app_code }];
if (a.requirements) files.push({ path: 'requirements.txt', content: a.requirements });
if (a.readme) files.push({ path: 'README.md', content: a.readme });
if (a.extra_files) {
var extras = parseJsonArg(a.extra_files);
for (var k in extras) if (Object.prototype.hasOwnProperty.call(extras, k)) files.push({ path: k, content: String(extras[k]) });
}
await commitFiles('space', p.namespace, p.name, files, null, a.summary || 'Deploy app via HuggingBot', 'Uploaded app.py' + (a.requirements ? ' + requirements.txt' : '') + (a.readme ? ' + README.md' : ''));
return ['βœ… **App deployed to:** `' + a.repo_id + '`', 'πŸ“¦ **Files uploaded:** ' + files.map(function (f) { return '`' + f.path + '`'; }).join(', '), '', '🌐 **Space:** https://huggingface.co/spaces/' + a.repo_id, 'πŸš€ **App URL:** ' + repoAppUrl(a.repo_id), '', 'πŸ’‘ The Space is now building. Use `hf_space_status ' + a.repo_id + '` to check progress.'].join('\n');
},
hf_delete_file: async function (a) {
var p = parseRepoId(a.repo_id);
await commitFiles('space', p.namespace, p.name, null, [{ path: a.path }], a.summary || ('Delete ' + a.path), '');
return 'βœ… **File deleted:** `' + a.path + '` from `' + a.repo_id + '`';
},
hf_set_secret: async function (a) {
var p = parseRepoId(a.repo_id);
try {
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'PUT', body: JSON.stringify({ key: a.key, value: a.value }) });
} catch (e) {
if (String(e.message).indexOf('404') > -1 || String(e.message).indexOf('405') > -1) {
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'POST', body: JSON.stringify({ key: a.key, value: a.value }) });
} else { throw e; }
}
return 'πŸ” **Secret set:** `' + a.key + '` in `' + a.repo_id + '`\n⚠️ The Space will restart.';
},
hf_delete_secret: async function (a) {
var p = parseRepoId(a.repo_id);
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/secrets', { method: 'DELETE', body: JSON.stringify({ key: a.key }) });
return 'πŸ—‘οΈ **Secret deleted:** `' + a.key + '` from `' + a.repo_id + '`';
},
hf_set_hardware: async function (a) {
var p = parseRepoId(a.repo_id);
var settings = { flavor: a.hardware };
if (a.sleep_time) settings.gcTimeout = a.sleep_time;
await updateSpaceSettings(p.namespace, p.name, settings);
var out = ['⚑ **Hardware change requested for:** `' + a.repo_id + '`', '**New hardware:** ' + a.hardware];
if (a.sleep_time) out.push('**Sleep timeout:** ' + a.sleep_time + 's');
out.push('', '⏳ The Space is restarting. Check status with `hf_space_status`.');
return out.join('\n');
},
hf_set_visibility: async function (a) {
var p = parseRepoId(a.repo_id);
await updateSpaceSettings(p.namespace, p.name, { private: a.visibility === 'private' });
return (a.visibility === 'private' ? 'πŸ”’' : '🌍') + ' **Visibility changed to ' + a.visibility + ':** `' + a.repo_id + '`';
},
hf_set_sleep: async function (a) {
var p = parseRepoId(a.repo_id);
await updateSpaceSettings(p.namespace, p.name, { gcTimeout: a.sleep_time });
var status = a.sleep_time === 0 ? '❌ Disabled (never sleeps)' : 'πŸ’€ ' + a.sleep_time + 's (' + Math.round(a.sleep_time / 60) + ' min)';
return 'βœ… **Sleep configured:** `' + a.repo_id + '` β†’ ' + status;
},
hf_pause_space: async function (a) {
var p = parseRepoId(a.repo_id);
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/pause', { method: 'POST' });
return '⏸️ **Space paused:** `' + a.repo_id + '`\nπŸ’‘ Restart with `hf_restart_space ' + a.repo_id + '`';
},
hf_restart_space: async function (a) {
var p = parseRepoId(a.repo_id);
await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/restart', { method: 'POST' });
return '▢️ **Space restarting:** `' + a.repo_id + '`';
},
hf_space_status: async function (a) {
var p = parseRepoId(a.repo_id);
var runtime = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime');
var repoInfo = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name);
var emoji = { 'NO_APP_FILE': 'πŸ“„', 'BUILDING': 'πŸ”¨', 'RUNNING': 'βœ…', 'RUNNING_BUILDING': 'πŸ”„', 'PAUSED': '⏸️', 'SLEEPING': 'πŸ’€', 'STOPPED': 'β›”', 'ERROR': '❌' };
var stage = runtime.stage || 'UNKNOWN';
var out = ['**πŸ“Š Space Status:** `' + a.repo_id + '`', '', '**Stage:** ' + (emoji[stage] || '❓') + ' ' + stage, '**SDK:** ' + (repoInfo.sdk || 'N/A'), '**Hardware:** ' + extractHardwareName(runtime.hardware), '**Requested HW:** ' + extractHardwareName(runtime.requestedHardware), '**Visibility:** ' + (repoInfo.private ? 'πŸ”’ Private' : '🌍 Public'), '**Likes:** ⭐ ' + (repoInfo.likes || 0), '**URL:** https://huggingface.co/spaces/' + a.repo_id, '**App:** ' + repoAppUrl(a.repo_id)];
return out.join('\n');
},
hf_space_logs: async function (a) {
var p = parseRepoId(a.repo_id);
var endpoints = [
'/api/spaces/' + p.namespace + '/' + p.name + '/runtime/logs',
'/api/spaces/' + p.namespace + '/' + p.name + '/logs',
'/api/spaces/' + p.namespace + '/' + p.name + '/runtime'
];
var lastError = null;
for (var i = 0; i < endpoints.length; i++) {
try {
var result = await hfFetch(endpoints[i]);
if (result && typeof result === 'object') {
var logsStr = result.logs || result.stdout || result.stderr || result.build || JSON.stringify(result, null, 2);
var truncated = logsStr.length > 5000 ? logsStr.slice(-5000) + '\n... (truncated, full: ' + logsStr.length + ' chars)' : logsStr;
return ['πŸ“‹ **Build Logs for** `' + a.repo_id + '`', '```', truncated, '```'].join('\n');
}
} catch (e) { lastError = e; }
}
try {
var rt = await hfFetch('/api/spaces/' + p.namespace + '/' + p.name + '/runtime');
if (rt && rt.errorMessage) return '⚠️ **Space has an error:**\n```\n' + rt.errorMessage + '\n```';
return 'ℹ️ **Space is** ' + (rt.stage || 'unknown') + '. No logs available via REST API.\n\nπŸ’‘ Logs can be viewed in the HF UI: https://huggingface.co/spaces/' + a.repo_id;
} catch (e2) { throw lastError || e2; }
},
hf_search_spaces: async function (a) {
var limit = Math.min(Math.max(a.limit || 5, 1), 25);
var results = await hfFetch('/api/spaces?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes');
if (!results || !results.length) return 'πŸ” No Spaces found for "' + a.query + '"';
var lines = ['πŸ” **Spaces matching "' + a.query + '":**', ''];
results.forEach(function (s, i) {
var id = s.id || ((s.namespace || '?') + '/' + (s.name || '?'));
var sdk = s.sdk ? ' [' + s.sdk + ']' : '';
var likes = s.likes ? ' ⭐' + s.likes : '';
var desc = s.description ? '\n > ' + s.description.substring(0, 150) : '';
lines.push((i + 1) + '. **' + id + '**' + sdk + likes);
if (desc) lines.push(desc);
lines.push(' https://huggingface.co/spaces/' + id);
lines.push('');
});
return lines.join('\n');
},
hf_list_my_spaces: async function () {
var userInfo = await hfFetch('/api/whoami-v2');
var name = userInfo.name || userInfo.user;
var results = await hfFetch('/api/spaces?author=' + encodeURIComponent(name) + '&limit=25&sort=lastModified');
if (!results || !results.length) return 'πŸ“­ No Spaces found under **' + name + '**.\nCreate one with `hf_create_space`!';
var lines = ['πŸ“‚ **Your Spaces (' + name + '):**', ''];
results.forEach(function (s) {
var id = s.id || (s.namespace + '/' + s.name);
var sdk = s.sdk ? ' [' + s.sdk + ']' : '';
var vis = s.private ? 'πŸ”’' : '🌍';
var likes = s.likes ? ' ⭐' + s.likes : '';
var stage = (s.runtime && s.runtime.stage) || '';
lines.push(vis + ' **' + id + '**' + sdk + likes + (stage ? ' β€” ' + stage : ''));
});
lines.push('', 'πŸ’‘ Use `hf_space_status <id>` for details on any Space.');
return lines.join('\n');
},
hf_search_models: async function (a) {
var limit = Math.min(Math.max(a.limit || 5, 1), 25);
var results = await hfFetch('/api/models?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes');
if (!results || !results.length) return 'πŸ” No models found for "' + a.query + '"';
var lines = ['πŸ” **Models matching "' + a.query + '":**', ''];
results.forEach(function (m, i) {
var id = m.id || ((m.namespace || '?') + '/' + (m.name || '?'));
var pipeline = m.pipeline_tag ? ' [' + m.pipeline_tag + ']' : '';
var likes = m.likes ? ' ⭐' + m.likes : '';
var downloads = m.downloads ? ' πŸ“₯' + (m.downloads / 1000).toFixed(0) + 'k' : '';
lines.push((i + 1) + '. **' + id + '**' + pipeline + likes + downloads);
lines.push(' https://huggingface.co/' + id);
});
return lines.join('\n');
},
hf_search_datasets: async function (a) {
var limit = Math.min(Math.max(a.limit || 5, 1), 25);
var results = await hfFetch('/api/datasets?search=' + encodeURIComponent(a.query) + '&limit=' + limit + '&sort=likes');
if (!results || !results.length) return 'πŸ” No datasets found for "' + a.query + '"';
var lines = ['πŸ” **Datasets matching "' + a.query + '":**', ''];
results.forEach(function (d, i) {
var id = d.id || ((d.namespace || '?') + '/' + (d.name || '?'));
var likes = d.likes ? ' ⭐' + d.likes : '';
var downloads = d.downloads ? ' πŸ“₯' + (d.downloads / 1000).toFixed(0) + 'k' : '';
lines.push((i + 1) + '. **' + id + '**' + likes + downloads);
lines.push(' https://huggingface.co/datasets/' + id);
});
return lines.join('\n');
},
hf_call_space: async function (a) {
var p = parseRepoId(a.repo_id);
var inputs = parseJsonArg(a.data);
if (!Array.isArray(inputs)) inputs = [inputs];
var apiName = String(a.api_name || '/predict').replace(/^\//, '');
var host = repoAppUrl(a.repo_id);
// Modern Gradio: POST /api/{name} with {data:[...]}; older: /api/predict
var tryEndpoints = ['/api/' + apiName, '/api/predict'];
var lastError = null;
for (var i = 0; i < tryEndpoints.length; i++) {
try {
var res = await fetch(host + tryEndpoints[i], {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: inputs })
});
if (res.ok) {
var out = await res.json();
if (out && out.data !== undefined) return 'βœ… **Result from `' + a.repo_id + '`' + (tryEndpoints[i] === '/api/predict' ? ' (legacy /predict)' : '') + ':**\n\n' + JSON.stringify(out.data, null, 2);
return 'βœ… **Raw response from `' + a.repo_id + '`:**\n\n' + JSON.stringify(out, null, 2);
}
lastError = new Error('HTTP ' + res.status + ' ' + res.statusText);
} catch (e) { lastError = e; }
}
throw new Error('Could not call Gradio API on ' + host + ': ' + (lastError ? lastError.message : 'unknown error') + '. The Space may be paused/sleeping or api_name may differ.');
},
hf_inference: async function (a) {
var token = getToken();
if (!token) throw new Error('No HuggingFace token set. hf_inference uses your token for higher rate limits.');
var url = INFER_API_BASE + (a.task ? '/pipeline/' + encodeURIComponent(a.task) + '/' : '/models/') + a.model;
var payload = { inputs: a.inputs };
if (a.parameters) payload.parameters = parseJsonArg(a.parameters);
var res = await fetch(url, {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
var em = '';
try { var ej = await res.json(); em = ej.error || JSON.stringify(ej); } catch (e) { em = res.statusText; }
throw new Error('Inference API ' + res.status + ': ' + em);
}
var ct = res.headers.get('content-type') || '';
if (ct.indexOf('image/') > -1 || ct.indexOf('audio/') > -1) {
var buf = await res.arrayBuffer();
var bytes = new Uint8Array(buf);
var bin = '';
for (var i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
var b64 = btoa(bin);
var mime = ct.split(';')[0];
if (mime.indexOf('image') > -1) return 'βœ… **Generated ' + mime + '** (' + (bytes.length / 1024).toFixed(1) + ' KB)\n\n![inference-result](data:' + mime + ';base64,' + b64 + ')';
return 'βœ… **Generated ' + mime + '** (' + (bytes.length / 1024).toFixed(1) + ' KB)\n\n' + (mime.indexOf('audio') > -1 ? '[πŸ”Š Audio result](data:' + mime + ';base64,' + b64 + ')' : 'Binary result in base64: `' + b64.substring(0, 120) + '…`');
}
var data = await res.json();
if (Array.isArray(data)) {
var parts = ['βœ… **Inference result from `' + a.model + '`:**', ''];
data.forEach(function (item) {
if (item && typeof item === 'object') {
var g = item.generated_text || item.label || item.answer || item.summary_text || item.translation_text || JSON.stringify(item);
parts.push(g);
} else { parts.push(String(item)); }
});
return parts.join('\n');
}
return 'βœ… **Inference result from `' + a.model + '`:**\n\n' + (typeof data === 'string' ? data : JSON.stringify(data, null, 2));
},
hf_repo_info: async function (a) {
var type = a.type || 'space';
var info = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id);
var lines = ['**πŸ“¦ Repo Info:** `' + a.repo_id + '` (' + type + ')', ''];
var fields = {
id: 'ID', name: 'Name', author: 'Author', pipeline_tag: 'Pipeline', library_name: 'Library',
sdk: 'SDK', sdk_version: 'SDK Version', likes: '⭐ Likes', downloads: 'πŸ“₯ Downloads',
license: 'License', private: 'Private', lastModified: 'Last Modified', createdAt: 'Created',
tags: 'Tags', description: 'Description', cardData: 'Card Data', sha: 'Commit SHA'
};
for (var k in fields) {
if (info[k] !== undefined && info[k] !== null && info[k] !== '') {
var v = info[k];
if (typeof v === 'object') v = JSON.stringify(v);
if (k === 'private') v = v ? 'πŸ”’ Yes' : '🌍 No';
if (String(v).length > 200) v = String(v).substring(0, 200) + '…';
lines.push('**' + fields[k] + ':** ' + v);
}
}
lines.push('', 'πŸ”— https://huggingface.co/' + type + 's/' + a.repo_id);
return lines.join('\n');
},
hf_list_files: async function (a) {
var type = a.type || 'space';
var path = a.path || '';
var rev = a.revision || 'main';
var url = '/api/' + repoTypePath(type) + '/' + a.repo_id + '/tree/' + encodeURIComponent(rev) + (path ? '/' + path.split('/').map(encodeURIComponent).join('/') : '') + '?recursive=false&expand=false';
var files = await hfFetch(url);
if (!files || !files.length) return 'πŸ“ No files found at `' + (path || '/') + '` in `' + a.repo_id + '`';
var lines = ['πŸ“‚ **Files in `' + a.repo_id + '`' + (path ? '/' + path : '') + ' (' + rev + '):**', ''];
files.forEach(function (f) {
var icon = f.type === 'directory' ? 'πŸ“' : 'πŸ“„';
var size = f.size !== undefined && f.size !== null ? ' β€” ' + (f.size / 1024).toFixed(1) + ' KB' : '';
lines.push(icon + ' `' + (f.path || f.name) + '`' + (f.type === 'directory' ? '/' : '') + size);
});
return lines.join('\n');
},
hf_read_file: async function (a) {
var type = a.type || 'space';
var rev = a.revision || 'main';
var rawPath = a.path.split('/').map(encodeURIComponent).join('/');
var text = await hfFetchRaw('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + encodeURIComponent(rev) + '/' + rawPath);
var truncated = text.length > 6000 ? text.substring(0, 6000) + '\n\n… (truncated, full: ' + text.length + ' chars)' : text;
return 'πŸ“„ **`' + a.path + '`** from `' + a.repo_id + '` (' + rev + '):\n\n```\n' + truncated + '\n```';
},
hf_file_exists: async function (a) {
var type = a.type || 'space';
var rev = a.revision || 'main';
var rawPath = a.path.split('/').map(encodeURIComponent).join('/');
var head = await hfFetchHead('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + encodeURIComponent(rev) + '/' + rawPath);
return head.ok
? 'βœ… **File exists:** `' + a.path + '` in `' + a.repo_id + '` (HTTP ' + head.status + ')'
: '❌ **File not found:** `' + a.path + '` in `' + a.repo_id + '` (HTTP ' + head.status + ')';
},
hf_repo_exists: async function (a) {
var type = a.type || 'space';
try {
var info = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id);
return 'βœ… **Repo exists:** `' + a.repo_id + '` (' + type + (info.private ? ', πŸ”’ private' : ', 🌍 public') + ')';
} catch (e) {
return '❌ **Repo not found or inaccessible:** `' + a.repo_id + '` (' + e.message + ')';
}
},
hf_list_commits: async function (a) {
var type = a.type || 'space';
var rev = a.revision || 'main';
var limit = Math.min(Math.max(a.limit || 10, 1), 50);
var commits = await hfFetch('/api/' + repoTypePath(type) + '/' + a.repo_id + '/commits/' + encodeURIComponent(rev) + '?limit=' + limit);
if (!commits || !commits.length) return 'πŸ“­ No commits found for `' + a.repo_id + '`';
var lines = ['πŸ—‚οΈ **Commit history for `' + a.repo_id + '` (' + rev + '):**', ''];
commits.forEach(function (c, i) {
var date = c.createdAt ? new Date(c.createdAt).toLocaleString() : '?';
var short = (c.id || '').substring(0, 7);
lines.push((i + 1) + '. **' + short + '** β€” ' + c.title + '');
lines.push(' πŸ“… ' + date + (c.author ? ' | πŸ‘€ ' + c.author.name : ''));
});
return lines.join('\n');
},
hf_move_file: async function (a) {
var p = parseRepoId(a.repo_id);
var type = a.type || 'space';
var rev = 'main';
// Move = read raw source, write to dest, delete source (works for all repo types via commit API)
var src = a.source_path.split('/').map(encodeURIComponent).join('/');
var content = await hfFetchRaw('/' + repoTypePath(type) + '/' + a.repo_id + '/raw/' + rev + '/' + src);
await commitFiles(type, p.namespace, p.name, [{ path: a.dest_path, content: content }], [{ path: a.source_path }], a.summary || 'Move/Rename file', 'Moved ' + a.source_path + ' β†’ ' + a.dest_path);
return 'πŸ“¦ **File moved:** `' + a.source_path + '` β†’ `' + a.dest_path + '` in `' + a.repo_id + '`';
}
};
// ─── Register module ───────────────────────────────────────────────────
MS.register('huggingface', {
name: 'HuggingFace Hub',
description: 'Full HuggingFace Hub control: auth, Spaces CRUD, file ops, secrets/hardware/sleep config, hub search, model inference, Gradio API calls, and repo browsing (expanded).',
tools: TOOLS.map(function (t) { return { type: 'function', function: t }; }),
handleToolCall: async function (tn, args) {
var fn = REGISTRY[tn];
if (!fn) return null;
try {
var out = await fn(args || {});
return typeof out === 'string' ? out : JSON.stringify(out, null, 2);
} catch (e) {
return '❌ ' + (e && e.message ? e.message : String(e));
}
}
});
})(ModuleSystem);