Spaces:
Running
Running
File size: 52,663 Bytes
3bc0be8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 | // 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';
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); |